Class: Feat::Client
- Inherits:
-
Object
- Object
- Feat::Client
- Includes:
- InterruptibleSleep
- Defined in:
- lib/feat/client.rb
Overview
HTTP client. Uses stdlib only - zero gem dependencies.
By default the client streams datafile updates over Server-Sent Events and keeps a slow background poll as a safety net. Disable streaming with ‘streaming: false` to fall back to polling alone.
Constant Summary collapse
- DEFAULT_URL =
"https://data-01.feat.so"- DEFAULT_POLL_INTERVAL =
30.0- DEFAULT_SAFETY_POLL_INTERVAL =
When streaming carries updates, the poll is a backstop only and runs far less often.
300.0- MIN_POLL_INTERVAL =
5.0- MAX_DATAFILE_BYTES =
10 * 1024 * 1024
- OPEN_TIMEOUT_SECONDS =
3- READ_TIMEOUT_SECONDS =
10- STREAM_READ_TIMEOUT_SECONDS =
Long-lived stream read: heartbeat comments keep it well under this.
90- POLL_JOIN_TIMEOUT_SECONDS =
Bound on how long #close waits for the poll thread to unwind, so a blocked fetch cannot make shutdown hang indefinitely.
5- RETRYABLE_CONNECT_ERRORS =
[ Net::OpenTimeout, Errno::ETIMEDOUT, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, ].freeze
Constants included from InterruptibleSleep
InterruptibleSleep::SLEEP_GRANULARITY
Instance Method Summary collapse
- #close ⇒ Object
- #evaluate(flag_key, default_value, ctx) ⇒ Object
- #get_boolean_value(flag_key, default, ctx) ⇒ Object
- #get_number_value(flag_key, default, ctx) ⇒ Object
- #get_object_value(flag_key, default, ctx) ⇒ Object
- #get_string_value(flag_key, default, ctx) ⇒ Object
-
#initialize(api_key:, url: DEFAULT_URL, poll_interval: DEFAULT_POLL_INTERVAL, streaming: true, safety_poll_interval: DEFAULT_SAFETY_POLL_INTERVAL, http_client: nil, stream_transport: nil) ⇒ Client
constructor
A new instance of Client.
- #refresh ⇒ Object
-
#start ⇒ Object
Blocking initial fetch; spawns the background poller (and stream).
Constructor Details
#initialize(api_key:, url: DEFAULT_URL, poll_interval: DEFAULT_POLL_INTERVAL, streaming: true, safety_poll_interval: DEFAULT_SAFETY_POLL_INTERVAL, http_client: nil, stream_transport: nil) ⇒ Client
Returns a new instance of Client.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/feat/client.rb', line 40 def initialize(api_key:, url: DEFAULT_URL, poll_interval: DEFAULT_POLL_INTERVAL, streaming: true, safety_poll_interval: DEFAULT_SAFETY_POLL_INTERVAL, http_client: nil, stream_transport: nil) raise ArgumentError, "api_key is required" if api_key.nil? || api_key.empty? assert_https_url!(url) @api_key = api_key @url = url.chomp("/") @streaming_enabled = streaming base_interval = streaming ? safety_poll_interval : poll_interval @poll_interval = [base_interval.to_f, MIN_POLL_INTERVAL].max @http_client = http_client @datafile = nil @etag = nil @mutex = Mutex.new @stop = false @thread = nil @sticky_ip = nil @streaming = build_streaming_client(stream_transport) if @streaming_enabled end |
Instance Method Details
#close ⇒ Object
70 71 72 73 74 75 76 77 78 |
# File 'lib/feat/client.rb', line 70 def close @stop = true @streaming&.stop # Join the poll thread (bounded) so a fetch in flight is waited out and # @thread is cleared, leaving the client cleanly restartable. @thread&.join(POLL_JOIN_TIMEOUT_SECONDS) @thread = nil self end |
#evaluate(flag_key, default_value, ctx) ⇒ Object
84 85 86 87 88 89 90 91 92 93 |
# File 'lib/feat/client.rb', line 84 def evaluate(flag_key, default_value, ctx) df = @datafile if df.nil? return EvaluationResult.new( value: default_value, reason: Reason::ERROR, error_message: "client not ready: call #start before #evaluate" ) end Eval.call(flag_key: flag_key, default_value: default_value, ctx: ctx, datafile: df) end |
#get_boolean_value(flag_key, default, ctx) ⇒ Object
95 96 97 98 |
# File 'lib/feat/client.rb', line 95 def get_boolean_value(flag_key, default, ctx) r = evaluate(flag_key, default, ctx) r.value == true || r.value == false ? r.value : default end |
#get_number_value(flag_key, default, ctx) ⇒ Object
105 106 107 108 |
# File 'lib/feat/client.rb', line 105 def get_number_value(flag_key, default, ctx) r = evaluate(flag_key, default, ctx) r.value.is_a?(Numeric) && !(r.value == true || r.value == false) ? r.value : default end |
#get_object_value(flag_key, default, ctx) ⇒ Object
110 111 112 113 |
# File 'lib/feat/client.rb', line 110 def get_object_value(flag_key, default, ctx) r = evaluate(flag_key, default, ctx) r.value end |
#get_string_value(flag_key, default, ctx) ⇒ Object
100 101 102 103 |
# File 'lib/feat/client.rb', line 100 def get_string_value(flag_key, default, ctx) r = evaluate(flag_key, default, ctx) r.value.is_a?(String) ? r.value : default end |
#refresh ⇒ Object
80 81 82 |
# File 'lib/feat/client.rb', line 80 def refresh fetch_once end |
#start ⇒ Object
Blocking initial fetch; spawns the background poller (and stream).
63 64 65 66 67 68 |
# File 'lib/feat/client.rb', line 63 def start refresh @streaming&.start @thread ||= Thread.new { poll_loop } self end |