use std::sync::{Arc, Mutex, MutexGuard};
use std::time::{Duration, SystemTime};
use codoseo_core::crawl::Politeness;
use reqwest::header::{HeaderMap, RETRY_AFTER};
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
use tokio::time::{Instant, sleep_until};
pub const MAX_RETRY_AFTER: Duration = Duration::from_secs(60);
struct State {
interval: Duration,
next_slot: Instant,
paused_until: Instant,
}
pub struct Limiter {
state: Mutex<State>,
max_interval: Duration,
rate_gap: Duration,
site: Arc<Semaphore>,
global: Arc<Semaphore>,
}
#[derive(Debug)]
pub struct Permit {
_site: OwnedSemaphorePermit,
_global: OwnedSemaphorePermit,
}
impl Limiter {
pub fn new(p: &Politeness, crawl_delay: Option<Duration>, global: Arc<Semaphore>) -> Limiter {
let max_interval = p.max_crawl_delay;
let rate_gap = if p.requests_per_sec.is_finite() && p.requests_per_sec > 0.0 {
Duration::from_secs_f64(1.0 / f64::from(p.requests_per_sec))
} else {
max_interval
};
let interval = rate_gap
.max(crawl_delay.unwrap_or(Duration::ZERO))
.min(max_interval);
let now = Instant::now();
Limiter {
state: Mutex::new(State {
interval,
next_slot: now,
paused_until: now,
}),
max_interval,
rate_gap,
site: Arc::new(Semaphore::new(p.per_site_connections.max(1) as usize)),
global,
}
}
pub async fn acquire(&self) -> Permit {
let site = self
.site
.clone()
.acquire_owned()
.await
.expect("the site semaphore is never closed");
loop {
let slot = {
let mut s = self.lock();
let slot = Instant::now().max(s.next_slot).max(s.paused_until);
s.next_slot = slot + s.interval;
slot
};
sleep_until(slot).await;
if self.lock().paused_until <= Instant::now() {
break;
}
}
let global = self
.global
.clone()
.acquire_owned()
.await
.expect("the global semaphore is never closed");
Permit {
_site: site,
_global: global,
}
}
pub fn on_response(&self, status: u16, retry_after: Option<Duration>) {
if status != 429 && status != 503 {
return;
}
let mut s = self.lock();
s.interval = s.interval.saturating_mul(2).min(self.max_interval);
let pause = retry_after
.unwrap_or_else(|| s.interval.saturating_mul(2))
.min(MAX_RETRY_AFTER);
s.paused_until = s.paused_until.max(Instant::now() + pause);
}
pub fn set_crawl_delay(&self, delay: Option<Duration>) {
let base = self
.rate_gap
.max(delay.unwrap_or(Duration::ZERO))
.min(self.max_interval);
let mut s = self.lock();
s.interval = s.interval.max(base);
}
pub fn interval(&self) -> Duration {
self.lock().interval
}
fn lock(&self) -> MutexGuard<'_, State> {
self.state.lock().unwrap_or_else(|e| e.into_inner())
}
}
pub fn parse_retry_after(value: &str, now: SystemTime) -> Option<Duration> {
let value = value.trim();
if let Ok(secs) = value.parse::<u64>() {
return Some(Duration::from_secs(secs));
}
let at = httpdate::parse_http_date(value).ok()?;
Some(at.duration_since(now).unwrap_or(Duration::ZERO))
}
pub(crate) fn retry_after_of(headers: &HeaderMap) -> Option<Duration> {
headers
.get(RETRY_AFTER)
.and_then(|v| v.to_str().ok())
.and_then(|v| parse_retry_after(v, SystemTime::now()))
}