Skip to main content

langfuse/
http.rs

1//! HTTP client utilities: shared client builder and retry logic.
2
3use backoff::ExponentialBackoffBuilder;
4use langfuse_core::config::LangfuseConfig;
5use langfuse_core::error::LangfuseError;
6use std::time::Duration;
7
8/// Build a `reqwest::Client` from config, applying timeout and additional headers.
9pub fn build_http_client(config: &LangfuseConfig) -> reqwest::Client {
10    let mut builder = reqwest::Client::builder().timeout(config.timeout);
11
12    if let Some(ref extra) = config.additional_headers {
13        let mut header_map = reqwest::header::HeaderMap::new();
14        for (k, v) in extra {
15            if let (Ok(name), Ok(val)) = (
16                reqwest::header::HeaderName::from_bytes(k.as_bytes()),
17                reqwest::header::HeaderValue::from_str(v),
18            ) {
19                header_map.insert(name, val);
20            }
21        }
22        builder = builder.default_headers(header_map);
23    }
24
25    builder.build().unwrap_or_else(|_| reqwest::Client::new())
26}
27
28/// Execute an async operation with exponential backoff retry.
29///
30/// Retries on:
31/// - 5xx server errors
32/// - 429 Too Many Requests
33/// - Network errors
34///
35/// Does NOT retry on:
36/// - 4xx client errors (except 429)
37/// - Auth errors
38pub async fn retry_request<F, Fut, T>(max_retries: usize, f: F) -> Result<T, LangfuseError>
39where
40    F: Fn() -> Fut,
41    Fut: std::future::Future<Output = Result<T, LangfuseError>>,
42{
43    let mut backoff = ExponentialBackoffBuilder::default()
44        .with_initial_interval(Duration::from_millis(100))
45        .with_max_interval(Duration::from_secs(30))
46        .with_max_elapsed_time(None)
47        .build();
48
49    let mut attempt = 0;
50    loop {
51        match f().await {
52            Ok(val) => return Ok(val),
53            Err(e) if should_retry(&e) && attempt < max_retries => {
54                attempt += 1;
55                if let Some(delay) = backoff::backoff::Backoff::next_backoff(&mut backoff) {
56                    tokio::time::sleep(delay).await;
57                } else {
58                    return Err(e);
59                }
60            }
61            Err(e) => return Err(e),
62        }
63    }
64}
65
66/// Determine if an error is retryable.
67fn should_retry(err: &LangfuseError) -> bool {
68    match err {
69        LangfuseError::Network(_) => true,
70        LangfuseError::Api { status, .. } => *status == 429 || *status >= 500,
71        _ => false,
72    }
73}