pub struct BudgetedHttpClient { /* private fields */ }Expand description
Wraps any HttpClient with the outbound budgets of issue #765:
a shared token bucket per upstream key, a daily quota with alerts, and
Retry-After-aware retries for idempotent calls. Adapters opt in by
wrapping the client they already hold — the runtime’s
BoundedHttpClient stays the
innermost hop, so the port’s own bounds
still apply underneath.
The streaming twin (HttpClient::send_streaming) spends the same
budget the same way before the head, then hands the body through
untouched: the retry loop covers the head only, because a body that
has started arriving can never be replayed.
Implementations§
Source§impl BudgetedHttpClient
impl BudgetedHttpClient
Sourcepub fn new(
inner: Arc<dyn HttpClient>,
clock: Arc<dyn Clock>,
db: Arc<dyn Database>,
router: BudgetRouter,
) -> Self
pub fn new( inner: Arc<dyn HttpClient>, clock: Arc<dyn Clock>, db: Arc<dyn Database>, router: BudgetRouter, ) -> Self
Budgets inner’s sends through router, reading wall time from
clock and spending buckets and quotas against db.
Sourcepub fn with_retry(self, policy: RetryPolicy) -> Self
pub fn with_retry(self, policy: RetryPolicy) -> Self
Replaces the retry policy.
Sourcepub fn with_events(self, bus: EventBus) -> Self
pub fn with_events(self, bus: EventBus) -> Self
Emits EVENT_QUOTA_WARNING / EVENT_QUOTA_EXHAUSTED to bus
as the daily quotas are crossed.
Sourcepub fn with_defer(self, defer: Arc<dyn Defer>) -> Self
pub fn with_defer(self, defer: Arc<dyn Defer>) -> Self
Runs the alert events’ handlers through this defer. Without it the
default NoopDefer drops them (with a warning), so a deployment
that subscribes to the quota events mounts the port’s defer here.
Sourcepub fn with_sleeper(self, sleeper: Sleeper) -> Self
pub fn with_sleeper(self, sleeper: Sleeper) -> Self
Replaces the sleep pacing and backoff wait through — the hook a runtime with a native timer (or a test that asserts on delays) supplies.
Sourcepub fn with_bucket_table(self, table: impl Into<String>) -> Self
pub fn with_bucket_table(self, table: impl Into<String>) -> Self
Renames the pacing-bucket table.
Sourcepub fn with_quota_table(self, table: impl Into<String>) -> Self
pub fn with_quota_table(self, table: impl Into<String>) -> Self
Renames the daily-quota table.
Sourcepub fn with_alert_table(self, table: impl Into<String>) -> Self
pub fn with_alert_table(self, table: impl Into<String>) -> Self
Renames the quota-alert claim table.
Sourcepub fn create_bucket_table_sql(table: &str) -> String
pub fn create_bucket_table_sql(table: &str) -> String
The portable DDL for the pacing-bucket table. The daily-quota table
is Usage::create_table_sql and the quota-alert claim table is
create_alert_table_sql — a venture
budgets only when it has created all three.
Sourcepub fn create_alert_table_sql(table: &str) -> String
pub fn create_alert_table_sql(table: &str) -> String
The portable DDL for the quota-alert claim table: one row per
(subject, day, kind) emission already made, the at-most-once gate
behind EVENT_QUOTA_WARNING / EVENT_QUOTA_EXHAUSTED. The
claim’s subject column names the quota table too, so two clients
metering different tables never claim each other’s alerts. A
venture that wires with_events ships this as a migration beside
the other two; a claim that meets its absence creates it on demand
(the same DDL, IF NOT EXISTS) rather than dropping the alert.
Trait Implementations§
Source§impl HttpClient for BudgetedHttpClient
impl HttpClient for BudgetedHttpClient
Source§fn send<'life0, 'async_trait>(
&'life0 self,
request: Request<Bytes>,
) -> Pin<Box<dyn Future<Output = Result<Response<Bytes>, HttpError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn send<'life0, 'async_trait>(
&'life0 self,
request: Request<Bytes>,
) -> Pin<Box<dyn Future<Output = Result<Response<Bytes>, HttpError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
request and returns the fully-buffered response under the
bounds this port carries: the body is at most
MAX_RESPONSE_BYTES unless an HttpPolicy tightens it, the
exchange is bounded by that policy’s deadline
(enforced through BoundedHttpClient on runtimes that wrap),
the destination is vetted where real sockets are opened, and a
runtime that keeps a concurrency budget holds one for the send. Read moreSource§fn send_streaming<'life0, 'async_trait>(
&'life0 self,
request: Request<Bytes>,
) -> Pin<Box<dyn Future<Output = Result<Response<ByteStream>, HttpError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn send_streaming<'life0, 'async_trait>(
&'life0 self,
request: Request<Bytes>,
) -> Pin<Box<dyn Future<Output = Result<Response<ByteStream>, HttpError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
request and returns the response with its body as a
stream (issue #859): the status and headers are known when the
future resolves — the head is bounded by HttpPolicy::timeout,
the same deadline send answers under — and the bytes arrive chunk
by chunk afterwards, under the StreamPolicy the request carries. Read more