use pin_project_lite::pin_project;
use std::{
collections::HashMap,
convert::Infallible,
future::Future,
hash::Hash,
pin::Pin,
task::{Context, Poll},
time::{Duration, Instant},
};
use tower::{Layer, Service};
use crate::{
encode,
proto::{Error as HrpcError, RetryInfo},
request::BoxRequest,
};
#[derive(Clone)]
pub struct RateLimitLayer<ExtractKey, BypassForKey> {
rate: Rate,
extract_key: ExtractKey,
bypass_for_key: BypassForKey,
}
type ExtractKeyDefault = fn(&mut BoxRequest) -> Option<()>;
type BypassForKeyDefault = fn(&()) -> bool;
impl RateLimitLayer<ExtractKeyDefault, BypassForKeyDefault> {
pub fn new(num: u64, per: Duration) -> Self {
let rate = Rate::new(num, per);
RateLimitLayer {
rate,
extract_key: |_| None,
bypass_for_key: |_| false,
}
}
}
impl<ExtractKey, BypassForKey> RateLimitLayer<ExtractKey, BypassForKey> {
pub fn set_key_fns<NewBypassForKey, NewExtractKeyFn, NewKey>(
self,
extract: NewExtractKeyFn,
bypass: NewBypassForKey,
) -> RateLimitLayer<NewExtractKeyFn, NewBypassForKey>
where
NewBypassForKey: Fn(&NewKey) -> bool + Clone,
NewExtractKeyFn: Fn(&mut BoxRequest) -> Option<NewKey> + Clone,
NewKey: Eq + Hash,
{
RateLimitLayer {
rate: self.rate,
bypass_for_key: bypass,
extract_key: extract,
}
}
}
impl<BypassForKey, ExtractKey, Key, S> Layer<S> for RateLimitLayer<ExtractKey, BypassForKey>
where
ExtractKey: Fn(&mut BoxRequest) -> Option<Key> + Clone,
BypassForKey: Fn(&Key) -> bool + Clone,
Key: Eq + Hash,
{
type Service = RateLimit<S, ExtractKey, BypassForKey, Key>;
fn layer(&self, service: S) -> Self::Service {
RateLimit::new(
service,
self.rate,
self.extract_key.clone(),
self.bypass_for_key.clone(),
)
}
}
pub struct RateLimit<T, ExtractKey, BypassForKey, Key> {
inner: T,
rate: Rate,
global_state: State,
keyed_states: HashMap<Key, State>,
extract_key: ExtractKey,
bypass_for_key: BypassForKey,
}
#[derive(Debug)]
enum State {
Limited { after: Instant },
Ready { until: Instant, rem: u64 },
}
impl State {
fn new_ready(rate: &Rate) -> Self {
State::Ready {
rem: rate.num(),
until: Instant::now(),
}
}
}
impl<S, ExtractKey, BypassForKey, Key> RateLimit<S, ExtractKey, BypassForKey, Key>
where
ExtractKey: Fn(&mut BoxRequest) -> Option<Key>,
BypassForKey: Fn(&Key) -> bool,
Key: Eq + Hash,
{
pub fn new(
inner: S,
rate: Rate,
extract_key: ExtractKey,
bypass_for_key: BypassForKey,
) -> Self {
RateLimit {
inner,
global_state: State::new_ready(&rate),
rate,
extract_key,
bypass_for_key,
keyed_states: HashMap::new(),
}
}
pub fn get_ref(&self) -> &S {
&self.inner
}
pub fn get_mut(&mut self) -> &mut S {
&mut self.inner
}
pub fn into_inner(self) -> S {
self.inner
}
}
impl<S, ExtractKey, BypassForKey, Key> Service<BoxRequest>
for RateLimit<S, ExtractKey, BypassForKey, Key>
where
S: Service<BoxRequest, Response = BoxResponse, Error = Infallible>,
ExtractKey: Fn(&mut BoxRequest) -> Option<Key>,
BypassForKey: Fn(&Key) -> bool,
Key: Eq + Hash,
{
type Response = S::Response;
type Error = S::Error;
type Future = RateLimitFuture<S::Future>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Service::poll_ready(&mut self.inner, cx)
}
fn call(&mut self, mut request: BoxRequest) -> Self::Future {
let state = match (self.extract_key)(&mut request) {
Some(key) => {
if (self.bypass_for_key)(&key) {
let fut = Service::call(&mut self.inner, request);
return RateLimitFuture::ready(fut);
}
self.keyed_states
.entry(key)
.or_insert_with(|| State::new_ready(&self.rate))
}
None => &mut self.global_state,
};
match *state {
State::Ready { mut until, mut rem } => {
let now = Instant::now();
if now >= until {
until = now + self.rate.per();
rem = self.rate.num();
}
if rem > 1 {
rem -= 1;
*state = State::Ready { until, rem };
} else {
let after = Instant::now() + self.rate.per();
*state = State::Limited { after };
}
let fut = Service::call(&mut self.inner, request);
RateLimitFuture::ready(fut)
}
State::Limited { after } => {
let now = Instant::now();
if now < after {
tracing::trace!("rate limit exceeded.");
let after = after - now;
return RateLimitFuture::limited(after);
}
*state = State::Ready {
until: now + self.rate.per(),
rem: self.rate.num(),
};
let fut = Service::call(&mut self.inner, request);
RateLimitFuture::ready(fut)
}
}
}
}
pin_project! {
#[project = EnumProj]
enum RateLimitFutureInner<Fut> {
Ready { #[pin] fut: Fut },
Limited { after: Duration },
}
}
pin_project! {
pub struct RateLimitFuture<Fut> {
#[pin]
inner: RateLimitFutureInner<Fut>,
}
}
impl<Fut> RateLimitFuture<Fut> {
fn ready(fut: Fut) -> Self {
Self {
inner: RateLimitFutureInner::Ready { fut },
}
}
fn limited(after: Duration) -> Self {
Self {
inner: RateLimitFutureInner::Limited { after },
}
}
}
impl<Fut> Future for RateLimitFuture<Fut>
where
Fut: Future<Output = Result<BoxResponse, Infallible>>,
{
type Output = Result<BoxResponse, Infallible>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.project().inner.project() {
EnumProj::Ready { fut } => fut.poll(cx),
EnumProj::Limited { after } => {
let retry_after = after.as_secs_f64().ceil() as u32;
let retry_info = RetryInfo { retry_after };
let err = HrpcError::new_resource_exhausted("rate limited; please try later")
.with_details(encode::encode_protobuf_message(&retry_info).freeze());
Poll::Ready(Ok(err.into()))
}
}
}
}
use crate::response::BoxResponse;
#[doc(inline)]
pub use self::rate::Rate;
mod rate {
use std::time::Duration;
#[derive(Debug, Copy, Clone)]
pub struct Rate {
num: u64,
per: Duration,
}
impl Rate {
pub fn new(num: u64, per: Duration) -> Self {
assert!(num > 0);
assert!(per > Duration::from_millis(0));
Rate { num, per }
}
pub(crate) fn num(&self) -> u64 {
self.num
}
pub(crate) fn per(&self) -> Duration {
self.per
}
}
}