fire_scope/
fetch.rs

1use rand::Rng;
2use reqwest::Client;
3use std::time::Duration;
4use tokio::time::sleep;
5
6/// HTTP GETを1回だけ実行する。
7/// 成功時はレスポンスボディを文字列として返す。
8/// 失敗時はエラーを返す。
9async fn fetch_once(
10    client: &Client,
11    url: &str,
12) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
13    let resp = client.get(url).send().await?;
14    let text = resp.text().await?;
15    Ok(text)
16}
17
18/// HTTP GETによるデータ取得を、リトライ+指数バックオフ付きで行う。
19/// 成功時はレスポンス文字列を返す。
20/// `retry_attempts`回失敗した場合、エラーを返す。
21pub async fn fetch_with_retry(
22    client: &Client,
23    url: &str,
24) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
25    let retry_attempts = 10;
26
27    for i in 0..retry_attempts {
28        match fetch_once(client, url).await {
29            Ok(text) => {
30                return Ok(text);
31            }
32            Err(e) => {
33                eprintln!(
34                    "[fetch_with_retry] Error on attempt {}/{}: {}",
35                    i + 1,
36                    retry_attempts,
37                    e
38                );
39                let sleep_duration = calc_exponential_backoff_duration(i);
40                sleep(sleep_duration).await;
41            }
42        }
43    }
44
45    Err(format!(
46        "Failed to fetch data from {} after {} attempts.",
47        url, retry_attempts
48    )
49    .into())
50}
51
52/// 指数バックオフのスリープ時間を計算するヘルパー関数
53fn calc_exponential_backoff_duration(retry_count: u32) -> Duration {
54    let mut rng = rand::rng();
55    // 0.0 ~ 1.0 の乱数
56    let random_part: f64 = rng.random();
57
58    let base = 2u64.pow(retry_count);
59    let backoff_seconds = (base as f64) + random_part;
60    Duration::from_secs_f64(backoff_seconds)
61}