pub struct Client { /* private fields */ }Expand description
Traced outbound HTTP client with automatic retries and test-mock support.
Extracted from AppState via Axum’s extractor machinery — declare it as a
handler parameter to get a pre-configured instance that respects
[http.client] config and, in test builds, intercepts requests against any
registered mocks.
use autumn_web::prelude::*;
use autumn_web::http::Client;
#[get("/ping-upstream")]
async fn ping(client: Client) -> AutumnResult<&'static str> {
client.get("https://api.example.com/health").send().await?;
Ok("ok")
}You can also construct a standalone client outside of a handler:
use autumn_web::http::Client;
let client = Client::new();Implementations§
Source§impl Client
impl Client
Sourcepub fn new() -> Self
pub fn new() -> Self
Create a new client with default settings (30 s timeout, 3 retries on idempotent methods).
Sourcepub fn with_timeout(timeout: Duration) -> Self
pub fn with_timeout(timeout: Duration) -> Self
Create a client with a custom per-request timeout.
§Panics
Panics if the underlying TLS backend cannot be initialised (should not
happen with the default rustls-tls feature).
Sourcepub fn from_config(config: &HttpClientConfig) -> Self
pub fn from_config(config: &HttpClientConfig) -> Self
Create a client from [http.client] framework configuration.
§Panics
Panics if the underlying TLS backend cannot be initialised (should not
happen with the default rustls-tls feature).
Sourcepub fn from_state(state: &AppState) -> Self
pub fn from_state(state: &AppState) -> Self
Build a client from runtime application state.
When the server was started via AppBuilder, a single reqwest::Client
is registered in AppState at boot as a SharedReqwestClient. This
method clones that shared instance (O(1), preserves the connection pool)
instead of constructing a new one, eliminating per-request TCP/TLS
handshakes and DNS-resolver-spawn overhead.
Falls back to Self::new() for detached or test state that does not
carry a shared client.
Sourcepub fn named(&self, alias: &str) -> Self
pub fn named(&self, alias: &str) -> Self
Return a clone of this client scoped to the named alias.
When a [http.client.base_urls] entry exists for the alias the client
will prepend that URL to all relative paths. Mocks registered for the
alias via TestApp::http_mock will
match requests made through this named client.
Sourcepub fn with_base_url(&self, base_url: impl Into<String>) -> Self
pub fn with_base_url(&self, base_url: impl Into<String>) -> Self
Set (or override) the base URL prepended to relative request paths.
Sourcepub fn get(&self, url: impl AsRef<str>) -> RequestBuilder
pub fn get(&self, url: impl AsRef<str>) -> RequestBuilder
Build a GET request.
Sourcepub fn post(&self, url: impl AsRef<str>) -> RequestBuilder
pub fn post(&self, url: impl AsRef<str>) -> RequestBuilder
Build a POST request.
Sourcepub fn put(&self, url: impl AsRef<str>) -> RequestBuilder
pub fn put(&self, url: impl AsRef<str>) -> RequestBuilder
Build a PUT request.
Sourcepub fn patch(&self, url: impl AsRef<str>) -> RequestBuilder
pub fn patch(&self, url: impl AsRef<str>) -> RequestBuilder
Build a PATCH request.
Sourcepub fn delete(&self, url: impl AsRef<str>) -> RequestBuilder
pub fn delete(&self, url: impl AsRef<str>) -> RequestBuilder
Build a DELETE request.
Sourcepub fn head(&self, url: impl AsRef<str>) -> RequestBuilder
pub fn head(&self, url: impl AsRef<str>) -> RequestBuilder
Build a HEAD request.
Sourcepub fn get_ssrf_safe(&self, url: impl Into<String>) -> RequestBuilder
pub fn get_ssrf_safe(&self, url: impl Into<String>) -> RequestBuilder
Build an SSRF-safe GET request.
This is the composed safe path for fetching untrusted URLs. Before
connecting it resolves the host once, validates every resolved IP
against the built-in SSRF deny-list (is_blocked_ip) and rejects the
request if any address is blocked. The connection is then pinned to the
full set of validated addresses so reqwest cannot re-resolve the host
(closing the DNS-rebinding / TOCTOU window) yet can still fall back across
them in order if the first is unreachable. Redirects are followed manually up to
SSRF_SAFE_MAX_REDIRECTS hops, re-running resolve→validate→pin on each
hop; a hop that downgrades the scheme from https to http is rejected
as defence-in-depth.
The redirect count and per-hop validation honour a chained builder override (the built-in resolve→validate→pin and scheme-downgrade guards always apply):
- by default, up to
SSRF_SAFE_MAX_REDIRECTShops are followed; - a chained
no_redirectreturns the initial3xxverbatim — the initial URL is still resolved, validated and pinned, but no redirect is followed; - a chained
follow_redirects(max, validator)caps following atmaxhops (somax == 0turns the first3xxintoClientError::TooManyRedirects) and additionally runs the caller’svalidator(&next)on every hop, on top of the built-in guards.
Like the test-mock path, this custom send path bypasses the process-wide circuit-breaker registry to avoid entangling per-URL SSRF fetches with the shared per-host breaker state.
Env proxies are bypassed. Each pinned per-hop client is built with
.no_proxy(), so HTTP_PROXY / HTTPS_PROXY / ALL_PROXY are ignored
and the socket connects directly to the validated/pinned address. This is
required for the pin to hold: reqwest checks proxy interception before the
connector where the resolve() override applies, so a configured proxy
would otherwise receive the request and re-resolve the host — reopening
the DNS-rebinding / SSRF window this API closes.
Cannot be combined with pin_to. This path
performs its own per-hop resolve→validate→pin and never reads the
pin_to address, so an explicit pin would be silently ignored. Chaining
the two is therefore rejected at send time with
ClientError::PinNotAllowedWithSsrfSafe. Use pin_to alone for a
caller-chosen fixed address, or get_ssrf_safe alone for guarded
automatic per-hop pinning.
Trait Implementations§
Source§impl FromRequestParts<AppState> for Client
impl FromRequestParts<AppState> for Client
Source§type Rejection = Infallible
type Rejection = Infallible
Source§async fn from_request_parts(
_parts: &mut Parts,
state: &AppState,
) -> Result<Self, Infallible>
async fn from_request_parts( _parts: &mut Parts, state: &AppState, ) -> Result<Self, Infallible>
Auto Trait Implementations§
impl !RefUnwindSafe for Client
impl !UnwindSafe for Client
impl Freeze for Client
impl Send for Client
impl Sync for Client
impl Unpin for Client
impl UnsafeUnpin for Client
Blanket Implementations§
Source§impl<T> AggregateExpressionMethods for T
impl<T> AggregateExpressionMethods for T
Source§fn aggregate_distinct(self) -> Self::Outputwhere
Self: DistinctDsl,
fn aggregate_distinct(self) -> Self::Outputwhere
Self: DistinctDsl,
DISTINCT modifier for aggregate functions Read moreSource§fn aggregate_all(self) -> Self::Outputwhere
Self: AllDsl,
fn aggregate_all(self) -> Self::Outputwhere
Self: AllDsl,
ALL modifier for aggregate functions Read moreSource§fn aggregate_filter<P>(self, f: P) -> Self::Output
fn aggregate_filter<P>(self, f: P) -> Self::Output
Source§fn aggregate_order<O>(self, o: O) -> Self::Outputwhere
Self: OrderAggregateDsl<O>,
fn aggregate_order<O>(self, o: O) -> Self::Outputwhere
Self: OrderAggregateDsl<O>,
Source§impl<T> AutumnDependents for Twhere
T: ?Sized,
impl<T> AutumnDependents for Twhere
T: ?Sized,
Source§fn dependents() -> &'static [RuntimeDependentSpec]
fn dependents() -> &'static [RuntimeDependentSpec]
#[model] overrides via an inherent shadow when dependents exist.Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSend for T
impl<T> DowncastSend for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
Source§impl<S, T> FromRequest<S, ViaParts> for T
impl<S, T> FromRequest<S, ViaParts> for T
Source§type Rejection = <T as FromRequestParts<S>>::Rejection
type Rejection = <T as FromRequestParts<S>>::Rejection
Source§fn from_request(
req: Request<Body>,
state: &S,
) -> impl Future<Output = Result<T, <T as FromRequest<S, ViaParts>>::Rejection>>
fn from_request( req: Request<Body>, state: &S, ) -> impl Future<Output = Result<T, <T as FromRequest<S, ViaParts>>::Rejection>>
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoSql for T
impl<T> IntoSql for T
Source§fn into_sql<T>(self) -> Self::Expression
fn into_sql<T>(self) -> Self::Expression
self to an expression for Diesel’s query builder. Read moreSource§fn as_sql<'a, T>(&'a self) -> <&'a Self as AsExpression<T>>::Expression
fn as_sql<'a, T>(&'a self) -> <&'a Self as AsExpression<T>>::Expression
&self to an expression for Diesel’s query builder. Read moreSource§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> Read<Exclusive, BecauseExclusive> for Twhere
T: ?Sized,
Source§impl<T> RepositoryHooksClone for Twhere
T: Clone,
impl<T> RepositoryHooksClone for Twhere
T: Clone,
Source§fn autumn_clone(&self) -> T
fn autumn_clone(&self) -> T
Source§impl<T> RepositoryHooksDefault for Twhere
T: Default,
impl<T> RepositoryHooksDefault for Twhere
T: Default,
Source§fn autumn_default() -> T
fn autumn_default() -> T
Source§impl<T, Conn> RunQueryDsl<Conn> for T
impl<T, Conn> RunQueryDsl<Conn> for T
Source§fn execute<'conn, 'query>(
self,
conn: &'conn mut Conn,
) -> <Conn as AsyncConnectionCore>::ExecuteFuture<'conn, 'query>
fn execute<'conn, 'query>( self, conn: &'conn mut Conn, ) -> <Conn as AsyncConnectionCore>::ExecuteFuture<'conn, 'query>
Source§fn load<'query, 'conn, U>(
self,
conn: &'conn mut Conn,
) -> AndThen<Self::LoadFuture<'conn>, TryCollect<Self::Stream<'conn>, Vec<U>>>
fn load<'query, 'conn, U>( self, conn: &'conn mut Conn, ) -> AndThen<Self::LoadFuture<'conn>, TryCollect<Self::Stream<'conn>, Vec<U>>>
Source§fn load_stream<'conn, 'query, U>(
self,
conn: &'conn mut Conn,
) -> Self::LoadFuture<'conn>where
Conn: AsyncConnectionCore,
U: 'conn,
Self: LoadQuery<'query, Conn, U> + 'query,
fn load_stream<'conn, 'query, U>(
self,
conn: &'conn mut Conn,
) -> Self::LoadFuture<'conn>where
Conn: AsyncConnectionCore,
U: 'conn,
Self: LoadQuery<'query, Conn, U> + 'query,
Stream] with the returned rows. Read moreSource§fn get_result<'query, 'conn, U>(
self,
conn: &'conn mut Conn,
) -> AndThen<Self::LoadFuture<'conn>, LoadNext<Pin<Box<Self::Stream<'conn>>>>>
fn get_result<'query, 'conn, U>( self, conn: &'conn mut Conn, ) -> AndThen<Self::LoadFuture<'conn>, LoadNext<Pin<Box<Self::Stream<'conn>>>>>
Source§fn get_results<'query, 'conn, U>(
self,
conn: &'conn mut Conn,
) -> AndThen<Self::LoadFuture<'conn>, TryCollect<Self::Stream<'conn>, Vec<U>>>
fn get_results<'query, 'conn, U>( self, conn: &'conn mut Conn, ) -> AndThen<Self::LoadFuture<'conn>, TryCollect<Self::Stream<'conn>, Vec<U>>>
Vec with the affected rows. Read moreSource§impl<T> Scoped for T
impl<T> Scoped for T
Source§fn scope(ctx: &PolicyContext) -> ScopeQuery<'_, Self>
fn scope(ctx: &PolicyContext) -> ScopeQuery<'_, Self>
ScopeQuery for this type. Resolves the
registered scope at .load() time, not here.