fetch_ccip/
fetch.rs

1use rand::{Rng, 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                // 取得成功時はすぐ返す
31                return Ok(text);
32            }
33            Err(e) => {
34                eprintln!(
35                    "[fetch_with_retry] Error on attempt {}/{}: {}",
36                    i + 1,
37                    retry_attempts,
38                    e
39                );
40                // 指数バックオフ + ランダムスリープ
41                let sleep_duration = calc_exponential_backoff_duration(i);
42                sleep(sleep_duration).await;
43            }
44        }
45    }
46
47    Err(format!(
48        "Failed to fetch data from {} after {} attempts.",
49        url, retry_attempts
50    )
51    .into())
52}
53
54/// 指数バックオフのスリープ時間を計算するヘルパー関数
55fn calc_exponential_backoff_duration(retry_count: u32) -> Duration {
56    // ランダムな要素を加える
57    let mut rng = rng();
58    let random_part: f64 = rng.random();
59
60    let base = 2u64.pow(retry_count);
61    let backoff_seconds = (base as f64) + random_part;
62    Duration::from_secs_f64(backoff_seconds)
63}