Class: ConvertSdk::HttpClient
- Inherits:
-
Object
- Object
- ConvertSdk::HttpClient
- Defined in:
- lib/convert_sdk/http_client.rb
Overview
The single hardened HTTP port every SDK request flows through.
+HttpClient+ is the only file in the gem that touches +Net::HTTP+ (a cheap architectural regression test asserts this). Every request it sends carries the ConvertAgent wire invariant and bounded timeouts, and every failure it encounters is converted into a failed Response rather than raised — the port NEVER raises to callers, so the config fetch (Story 2.5) and event delivery (Story 4.1) consumers degrade gracefully on a failed response.
== The ConvertAgent wire invariant
The metrics endpoint's bot filter silently DROPS server-side events whose +User-Agent+ is not +ConvertAgent/1.0+. The header is therefore applied LAST, after every header merge, so it cannot be overridden by an integrator-supplied +User-Agent+. Without it, tracking events would vanish silently in production. (JS/PHP precedent: set unconditionally after merge.)
== Bounded timeouts
Both +open_timeout+ and +read_timeout+ are set explicitly on EVERY request (a deliberate improvement over the JS SDK, which sets none). The SDK can never hang a host thread waiting on a slow or dead endpoint.
== TLS / Bearer / proxies
HTTPS endpoints use TLS with verification ON (+verify_mode+ is never +VERIFY_NONE+). An +Authorization: Bearer ...+ header is stripped (and a warning logged) on any non-HTTPS endpoint so the SDK key secret never crosses the wire in plaintext. Proxies are honoured through the standard +Net::HTTP+ environment conventions (+http_proxy+/+https_proxy+/+no_proxy+).
== JSON boundary
Callers pass and receive Ruby hashes; JSON encode/decode happens only here. A request +body+ hash is rendered with +JSON.generate+; a response body is parsed with +JSON.parse+ (string keys). A parse failure is logged and yields +body: nil+ on an otherwise intact response.
All logging goes through the injected LogManager (never +puts+), so the Redactor masks secrets and strips URL query strings from every line.
Defined Under Namespace
Classes: Response
Constant Summary collapse
- USER_AGENT =
The mandatory wire User-Agent. Applied LAST so it is unoverridable.
"ConvertAgent/1.0"- FAILURE_STATUS =
The status used for a failed Response when no HTTP response was received (network error / timeout). Callers MUST use Response#success?, never compare the status integer, for error detection.
0
Instance Method Summary collapse
-
#initialize(log_manager:, open_timeout:, read_timeout:) ⇒ HttpClient
constructor
A new instance of HttpClient.
-
#request(method:, url:, headers: {}, body: nil) ⇒ Response
Send one HTTP request and return a frozen Response.
Constructor Details
#initialize(log_manager:, open_timeout:, read_timeout:) ⇒ HttpClient
Returns a new instance of HttpClient.
86 87 88 89 90 |
# File 'lib/convert_sdk/http_client.rb', line 86 def initialize(log_manager:, open_timeout:, read_timeout:) @log_manager = log_manager @open_timeout = open_timeout @read_timeout = read_timeout end |
Instance Method Details
#request(method:, url:, headers: {}, body: nil) ⇒ Response
Send one HTTP request and return a frozen Response. Never raises: any transport failure is logged and returned as a failed response.
101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/convert_sdk/http_client.rb', line 101 def request(method:, url:, headers: {}, body: nil) uri = URI.parse(url) https = uri.scheme == "https" wire_headers = build_headers(headers, https) @log_manager.debug("HttpClient#request: #{method.to_s.upcase} #{url}") perform(method, uri, https, wire_headers, body) rescue StandardError => e @log_manager.error("HttpClient#request: request failed (#{e.class}: #{e.})") failed_response end |