Class: VoiceML::Transport Private

Inherits:
Object
  • Object
show all
Defined in:
lib/voiceml/transport.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.

HTTP transport for the VoiceML REST API.

  • Auth: HTTP Basic with ‘account_sid` (Twilio-format `AC` + 32 hex) as the username and the per-tenant API key as the password. Drop-in compatible with the Twilio SDK constructor.

  • Wire format: requests are form-urlencoded by default (Twilio convention). The server also accepts JSON; pass ‘json: <hash>` to send JSON instead. Responses are always JSON.

  • Retries: 429 + 5xx are retried up to ‘max_retries` times with exponential backoff, honoring the `Retry-After` header when the server emits one.

  • Binary fetch: ‘fetch_bytes` follows the 302 -> S3 redirect that `GET /Recordings/sid.wav` issues when audio has been archived. Callers usually only care about the final bytes.

Constant Summary collapse

DEFAULT_BASE_URL =

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.

'https://voiceml.voicetel.com'
DEFAULT_TIMEOUT =

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.

30
DEFAULT_MAX_RETRIES =

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.

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

[429, 500, 502, 503, 504].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(account_sid:, api_key:, base_url: DEFAULT_BASE_URL, timeout: DEFAULT_TIMEOUT, max_retries: DEFAULT_MAX_RETRIES, user_agent: nil, http_client: nil) ⇒ Transport

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

Raises:



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/voiceml/transport.rb', line 40

def initialize(account_sid:, api_key:, base_url: DEFAULT_BASE_URL,
               timeout: DEFAULT_TIMEOUT, max_retries: DEFAULT_MAX_RETRIES,
               user_agent: nil, http_client: nil)
  raise ConfigurationError, 'account_sid is required' if .nil? || .empty?
  raise ConfigurationError, 'api_key is required'     if api_key.nil? || api_key.empty?
  raise ConfigurationError, 'max_retries must be >= 0' if max_retries.negative?

  @account_sid = 
  @api_key     = api_key
  @base_url    = self.class.strip_trailing_slashes(base_url)
  @timeout     = timeout
  @max_retries = max_retries
  @user_agent  = user_agent || "voiceml-ruby/#{VoiceML::VERSION}"
  @uri_base    = URI.parse(@base_url)
  @conn_mutex  = Mutex.new
  @owns_client = http_client.nil?
  @persistent  = http_client
end

Instance Attribute Details

#account_sidObject (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.



38
39
40
# File 'lib/voiceml/transport.rb', line 38

def 
  @account_sid
end

#base_urlObject (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.



38
39
40
# File 'lib/voiceml/transport.rb', line 38

def base_url
  @base_url
end

#max_retriesObject (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.



38
39
40
# File 'lib/voiceml/transport.rb', line 38

def max_retries
  @max_retries
end

#timeoutObject (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.



38
39
40
# File 'lib/voiceml/transport.rb', line 38

def timeout
  @timeout
end

#user_agentObject (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.



38
39
40
# File 'lib/voiceml/transport.rb', line 38

def user_agent
  @user_agent
end

Class Method Details

.strip_trailing_slashes(s) ⇒ 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.

Linear-time trailing-slash strip. Equivalent to ‘base_url.sub(%r/+z, ”)` but without the polynomial-backtracking shape CodeQL flags (`rb/polynomial-redos`).



32
33
34
35
36
# File 'lib/voiceml/transport.rb', line 32

def self.strip_trailing_slashes(s)
  i = s.length
  i -= 1 while i.positive? && s.getbyte(i - 1) == 47 # '/'
  i == s.length ? s : s[0, i]
end

Instance Method Details

#closeObject

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.

Close the persistent connection, releasing the underlying socket. Safe to call multiple times. The transport remains usable — the next request will transparently open a fresh connection.



62
63
64
# File 'lib/voiceml/transport.rb', line 62

def close
  @conn_mutex.synchronize { finish_connection } if @owns_client
end

#fetch_bytes(path) ⇒ Array(Integer, String, Hash)

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.

Fetch a binary payload (audio/wav recordings). Follows the single 302 -> presigned S3 redirect that ‘GET /Recordings/sid.wav` issues when audio has been archived.

Returns:

  • (Array(Integer, String, Hash))

    status code, response body bytes, header hash.



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/voiceml/transport.rb', line 98

def fetch_bytes(path)
  uri = build_uri(path, nil)
  visited = []
  loop do
    raise ApiError.new('too many redirects', status_code: 0) if visited.length > 5

    req = Net::HTTP::Get.new(uri)
    apply_common_headers(req, auth: !visited.any? { |u| u.host != uri.host })
    response = send_on_persistent(uri, req)

    case response
    when Net::HTTPRedirection
      visited << uri
      uri = URI.parse(response['location'])
      next
    when Net::HTTPSuccess
      headers = response.each_header.to_h
      return [response.code.to_i, response.body || '', headers]
    else
      raise_for_response(response)
    end
  end
end

#request(method, path, params: nil, form: nil, json: nil) ⇒ Hash, ...

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. Pass ‘form:` for a form-urlencoded POST body, `json:` for a JSON body, or neither for a plain GET/DELETE. `params:` are query-string params.

Returns:

  • (Hash, Array, nil)

    the parsed JSON body (or ‘nil` for empty 2xx).

Raises:



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/voiceml/transport.rb', line 71

def request(method, path, params: nil, form: nil, json: nil)
  uri = build_uri(path, params)

  attempt = 0
  loop do
    response = perform_request(method, uri, form: form, json: json)

    if RETRYABLE_STATUSES.include?(response.code.to_i) && attempt < @max_retries
      sleep(backoff_delay(attempt, response))
      attempt += 1
      next
    end

    return parse_response(response)
  rescue *transport_errors => e
    raise ApiError.new("transport error after #{attempt + 1} attempts: #{e.message}",
                       status_code: 0) if attempt >= @max_retries

    sleep(backoff_delay(attempt))
    attempt += 1
  end
end