A Client cloneable, created at boot. Not reqwest::get per request (no pool, no timeout). Runtime: tokio. Signed webhooks: webhooks. Contracts: api-design. Tests: rust-unit-testing (fake or wiremock if the crate already has it).
Non-negotiable rules
Clientin theAppState(orOnceLockin the binary). Clone is cheap (handle).- Timeouts:
connect+timeouttotal in the builder (and/or per request). No eternal hang. - Retry only on network /
429/5xxwith backoff and cap. Do not retry4xxbusiness. - JSON:
serdetypes. Status ≠ 2xx →AppErrormapped, notunwrapin thejson().await. - Do not log body with token/PII (
tracing).
Client
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(10))
.connect_timeout(Duration::from_secs(3))
.user_agent("myapp/1.0")
.build()?;
- TLS: default (rustls or native-tls from Cargo.toml). Do not disable verify in prod.
- HTTP/2: leave the crate default unless the partner requires it.
- Redirect: limit; outbound webhooks (
webhooks) — beware SSRF (private IPs) if the URL is user-supplied.
In Axum
async fn proxy(
State(st): State<AppState>,
Path(id): Path<Uuid>,
) -> Result<Json<Dto>, AppError> {
let res = st.http.get(url).send().await?;
let status = res.status();
let dto = res.json::<Dto>().await?;
// mapeie status
}
error_for_statusor explicit match.bytes()for HMAC signature (raw body).- Cancel: dropping the future on request abort cancels the I/O — do not ignore
JoinHandleof a loose spawn.
Retries
- Idempotent:
GET,PUTwith idempotency-key.POSTpayment: do not blind retry. - Jitter + cap. Circuit: only if the repo already has it (do not invent Hystrix).
background-jobsfor slow fan-out; the handler does not wait 30s for a partner.
Anti-patterns
reqwest::get(url).await?in a loop (new client)expect("http")on the hot path- Retry on 400/409
- Trust
Content-Typewithout serde - Mutable global Client reconfigured in the handler
Integration with other skills
- Runtime:
tokio - Logs/spans:
tracing - HTTP app:
axum - Callbacks:
webhooks - Production:
rust-axum-production(server timeouts ≠ client timeouts)
Completion criteria
- One
Clientin state; timeouts defined - HTTP errors mapped; typed JSON
- Retry only transient; critical POST without blind replay
- No PII in the log
- SSRF aware if the URL is input