feat: add configurable retry mechanism for API requests

main
Yoo1tic 2025-07-31 20:57:19 +08:00
parent 92c16fdc3d
commit 5f1b30772b
4 changed files with 41 additions and 19 deletions

View File

@ -3,6 +3,7 @@ output_path = "output_keys.txt"
backup_path = "backup_keys.txt"
api_host = "https://generativelanguage.googleapis.com/"
timeout_sec = 20
max_retries = 2
concurrency = 30
proxy = "http://username:password@host:port"
enable_multiplexing = true

View File

@ -39,6 +39,10 @@ struct Cli {
#[serde(skip_serializing_if = "Option::is_none")]
concurrency: Option<usize>,
#[arg(short = 'r', long)]
#[serde(skip_serializing_if = "Option::is_none")]
max_retries: Option<usize>,
#[arg(short = 'x', long)]
#[serde(skip_serializing_if = "Option::is_none")]
proxy: Option<Url>,
@ -66,6 +70,10 @@ pub struct KeyCheckerConfig {
#[serde(default)]
pub timeout_sec: u64,
// Maximum number of retries for failed requests.
#[serde(default)]
pub max_retries: usize,
// Maximum number of concurrent requests.
#[serde(default)]
pub concurrency: usize,
@ -154,6 +162,7 @@ static DEFAULT_CONFIG: LazyLock<KeyCheckerConfig> = LazyLock::new(|| KeyCheckerC
api_host: Url::parse("https://generativelanguage.googleapis.com/").unwrap(),
timeout_sec: 15,
concurrency: 50,
max_retries: 2,
proxy: None,
enable_multiplexing: true,
log_level: "info".to_string(),

View File

@ -4,7 +4,7 @@ use tokio::time::Duration;
use tracing::{debug, error, info, warn};
use url::Url;
use crate::config::TEST_MESSAGE_BODY;
use crate::config::{KeyCheckerConfig, TEST_MESSAGE_BODY};
use crate::error::ValidatorError;
use crate::types::GeminiKey;
@ -12,18 +12,24 @@ pub async fn validate_key(
client: Client,
api_endpoint: impl IntoUrl,
api_key: GeminiKey,
config: KeyCheckerConfig,
) -> Result<GeminiKey, ValidatorError> {
let api_endpoint = api_endpoint.into_url()?;
match send_test_request(client, &api_endpoint, api_key.clone()).await {
match send_test_request(client, &api_endpoint, api_key.clone(), config.max_retries).await {
Ok(_) => {
info!("SUCCESS - {}... - Valid key found", &api_key.as_ref()[..10]);
Ok(api_key)
}
Err(e) => {
match e.status() {
Some(StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN | StatusCode::TOO_MANY_REQUESTS) => {
warn!("INVALID - {}... - {}", &api_key.as_ref()[..10], ValidatorError::KeyInvalid);
Err(e) => match e.status() {
Some(
StatusCode::UNAUTHORIZED | StatusCode::FORBIDDEN | StatusCode::TOO_MANY_REQUESTS,
) => {
warn!(
"INVALID - {}... - {}",
&api_key.as_ref()[..10],
ValidatorError::KeyInvalid
);
Err(ValidatorError::KeyInvalid)
}
_ => {
@ -31,8 +37,7 @@ pub async fn validate_key(
error!("ERROR- {}... - {}", &api_key.as_ref()[..10], req_error);
Err(req_error)
}
}
}
},
}
}
@ -40,11 +45,12 @@ async fn send_test_request(
client: Client,
api_endpoint: &Url,
key: GeminiKey,
max_retries: usize,
) -> Result<reqwest::Response, reqwest::Error> {
let retry_policy = ExponentialBuilder::default()
.with_max_times(3)
.with_min_delay(Duration::from_secs(3))
.with_max_delay(Duration::from_secs(5));
.with_max_times(max_retries)
.with_min_delay(Duration::from_secs(1))
.with_max_delay(Duration::from_secs(2));
(async || {
let response = client

View File

@ -48,7 +48,14 @@ impl ValidationService {
// Create stream to validate keys concurrently
let valid_keys_stream = stream
.map(|key| validate_key(self.client.clone(), self.full_url.clone(), key))
.map(|key| {
validate_key(
self.client.clone(),
self.full_url.clone(),
key,
self.config.clone(),
)
})
.buffer_unordered(self.config.concurrency)
.filter_map(|result| async { result.ok() });
pin_mut!(valid_keys_stream);
@ -72,7 +79,6 @@ impl ValidationService {
}
}
pub async fn start_validation() -> Result<(), ValidatorError> {
let config = KeyCheckerConfig::load_config()?;