governor/middleware.rs
1//! Additional, customizable behavior for rate limiters.
2//!
3//! Rate-limiting middleware follows the principle that basic
4//! rate-limiting should be very cheap, and unless users desire more
5//! behavior, they should not pay any extra price.
6//!
7//! However, if you do desire more information about what the
8//! rate-limiter does (or the ability to install hooks in the
9//! decision-making process), you can. The [`RateLimitingMiddleware`]
10//! trait in this module allows you to customize:
11//!
12//! * Any additional code that gets run when a rate-limiting decision is made.
13//! * What value is returned in the positive or negative case.
14//!
15//! Writing middleware does **not** let you override rate-limiting
16//! decisions: They remain either positive (returning `Ok`) or negative
17//! (returning `Err`). However, you can override the values returned
18//! inside the Result for either decision.
19//!
20//! This crate ships two middlewares (named after their behavior in the
21//! positive outcome):
22//!
23//! * The cheapest still-useful one, [`NoOpMiddleware`], named after its
24//! behavior in the positive case. In the positive case it returns
25//! `Ok(())`; in the negative case, `Err(`[`NotUntil`]`)`.
26//!
27//! * A more informative middleware, [`StateInformationMiddleware`], which
28//! returns `Ok(`[`StateSnapshot`]`)`, or
29//! `Err(`[`NotUntil`]`)`.
30//!
31//! ## Using a custom middleware
32//!
33//! Middlewares are attached to the
34//! [`RateLimiter`][crate::RateLimiter] at construction time using
35//! [`RateLimiter::with_middleware`][crate::RateLimiter::with_middleware]:
36//!
37//! ```rust
38//! # #[cfg(feature = "std")]
39//! # fn main () {
40//! # use nonzero_ext::nonzero;
41//! use governor::{RateLimiter, Quota, middleware::StateInformationMiddleware};
42//! let lim = RateLimiter::direct(Quota::per_hour(nonzero!(1_u32)))
43//! .with_middleware::<StateInformationMiddleware>();
44//!
45//! // A positive outcome with additional information:
46//! assert!(
47//! lim.check()
48//! // Here we receive an Ok(StateSnapshot):
49//! .map(|outcome| assert_eq!(outcome.remaining_burst_capacity(), 0))
50//! .is_ok()
51//! );
52//!
53//! // The negative case:
54//! assert!(
55//! lim.check()
56//! // Here we receive Err(NotUntil):
57//! .map_err(|outcome| assert_eq!(outcome.quota().burst_size().get(), 1))
58//! .is_err()
59//! );
60//! # }
61//! # #[cfg(not(feature = "std"))]
62//! # fn main() {}
63//! ```
64//!
65//! You can define your own middleware by `impl`ing [`RateLimitingMiddleware`].
66use core::fmt;
67use core::{cmp, marker::PhantomData};
68
69use crate::{clock, nanos::Nanos, NotUntil, Quota};
70
71/// Information about the rate-limiting state used to reach a decision.
72#[derive(Clone, PartialEq, Eq, Debug)]
73pub struct StateSnapshot {
74 /// The "weight" of a single packet in units of time.
75 t: Nanos,
76
77 /// The "tolerance" of the bucket.
78 ///
79 /// The total "burst capacity" of the bucket is `t + tau`.
80 tau: Nanos,
81
82 /// The time at which the measurement was taken.
83 pub(crate) time_of_measurement: Nanos,
84
85 /// The next time a cell is expected to arrive
86 pub(crate) tat: Nanos,
87}
88
89impl StateSnapshot {
90 #[inline]
91 pub(crate) fn new(t: Nanos, tau: Nanos, time_of_measurement: Nanos, tat: Nanos) -> Self {
92 Self {
93 t,
94 tau,
95 time_of_measurement,
96 tat,
97 }
98 }
99
100 /// Returns the quota used to make the rate limiting decision.
101 pub fn quota(&self) -> Quota {
102 Quota::from_gcra_parameters(self.t, self.tau)
103 }
104
105 /// Returns the number of cells that can be let through in
106 /// addition to a (possible) positive outcome.
107 ///
108 /// If this state snapshot is based on a negative rate limiting
109 /// outcome, this method returns 0.
110 pub fn remaining_burst_capacity(&self) -> u32 {
111 let t0 = self.time_of_measurement;
112 (cmp::min(
113 (t0 + self.tau + self.t).saturating_sub(self.tat).as_u64(),
114 (self.tau + self.t).as_u64(),
115 ) / self.t.as_u64()) as u32
116 }
117}
118
119/// Defines the behavior and return values of rate limiting decisions.
120///
121/// While the rate limiter defines whether a decision is positive, the
122/// middleware defines what additional values (other than `Ok` or `Err`)
123/// are returned from the [`RateLimiter`][crate::RateLimiter]'s check methods.
124///
125/// The default middleware in this crate is [`NoOpMiddleware`] (which does
126/// nothing in the positive case and returns [`NotUntil`] in the
127/// negative) - so it does only the smallest amount of work it needs to do
128/// in order to be useful to users.
129///
130/// Other middleware gets to adjust these trade-offs: The pre-made
131/// [`StateInformationMiddleware`] returns quota and burst capacity
132/// information, while custom middleware could return a set of HTTP
133/// headers or increment counters per each rate limiter key's decision.
134///
135/// # Defining your own middleware
136///
137/// Here's an example of a rate limiting middleware that does no
138/// computations at all on positive and negative outcomes: All the
139/// information that a caller will receive is that a request should be
140/// allowed or disallowed. This can allow for faster negative outcome
141/// handling, and is useful if you don't need to tell users when they
142/// can try again (or anything at all about their rate limiting
143/// status).
144///
145/// ```rust
146/// # use std::num::NonZeroU32;
147/// # use nonzero_ext::*;
148/// use governor::{middleware::{RateLimitingMiddleware, StateSnapshot},
149/// Quota, RateLimiter, clock::Reference};
150/// # #[cfg(feature = "std")]
151/// # fn main () {
152/// #[derive(Debug)]
153/// struct NullMiddleware;
154///
155/// impl<P: Reference> RateLimitingMiddleware<P> for NullMiddleware {
156/// type PositiveOutcome = ();
157/// type NegativeOutcome = ();
158///
159/// fn allow<K>(_key: &K, _state: impl Into<StateSnapshot>) -> Self::PositiveOutcome {}
160/// fn disallow<K>(_: &K, _: impl Into<StateSnapshot>, _: P) -> Self::NegativeOutcome {}
161/// }
162///
163/// let lim = RateLimiter::direct(Quota::per_hour(nonzero!(1_u32)))
164/// .with_middleware::<NullMiddleware>();
165///
166/// assert_eq!(lim.check(), Ok(()));
167/// assert_eq!(lim.check(), Err(()));
168/// # }
169/// # #[cfg(not(feature = "std"))]
170/// # fn main() {}
171/// ```
172pub trait RateLimitingMiddleware<P: clock::Reference>: fmt::Debug {
173 /// The type that's returned by the rate limiter when a cell is allowed.
174 ///
175 /// By default, rate limiters return `Ok(())`, which does not give
176 /// much information. By using custom middleware, users can obtain
177 /// more information about the rate limiter state that was used to
178 /// come to a decision. That state can then be used to pass
179 /// information downstream about, e.g. how much burst capacity is
180 /// remaining.
181 type PositiveOutcome: Sized;
182
183 /// The type that's returned by the rate limiter when a cell is *not* allowed.
184 ///
185 /// By default, rate limiters return `Err(NotUntil)`, which
186 /// allows interrogating the minimum amount of time to wait until
187 /// a client can expect to have a cell allowed again.
188 type NegativeOutcome: Sized;
189
190 /// Called when a positive rate-limiting decision is made.
191 ///
192 /// This function is able to affect the return type of
193 /// [RateLimiter.check](../struct.RateLimiter.html#method.check)
194 /// (and others) in the Ok case: Whatever is returned here is the
195 /// value of the Ok result returned from the check functions.
196 ///
197 /// The function is passed a snapshot of the rate-limiting state
198 /// updated to *after* the decision was reached: E.g., if there
199 /// was one cell left in the burst capacity before the decision
200 /// was reached, the [`StateSnapshot::remaining_burst_capacity`]
201 /// method will return 0.
202 fn allow<K>(key: &K, state: impl Into<StateSnapshot>) -> Self::PositiveOutcome;
203
204 /// Called when a negative rate-limiting decision is made (the
205 /// "not allowed but OK" case).
206 ///
207 /// This method returns whatever value is returned inside the
208 /// `Err` variant a [`RateLimiter`][crate::RateLimiter]'s check
209 /// method returns.
210 fn disallow<K>(
211 key: &K,
212 limiter: impl Into<StateSnapshot>,
213 start_time: P,
214 ) -> Self::NegativeOutcome;
215}
216
217/// A middleware that does nothing and returns `()` in the positive outcome.
218pub struct NoOpMiddleware<P: clock::Reference = <clock::DefaultClock as clock::Clock>::Instant> {
219 phantom: PhantomData<P>,
220}
221
222impl<P: clock::Reference> core::fmt::Debug for NoOpMiddleware<P> {
223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224 write!(f, "NoOpMiddleware")
225 }
226}
227
228impl<P: clock::Reference> RateLimitingMiddleware<P> for NoOpMiddleware<P> {
229 /// By default, rate limiters return nothing other than an
230 /// indicator that the element should be let through.
231 type PositiveOutcome = ();
232
233 type NegativeOutcome = NotUntil<P>;
234
235 #[inline]
236 /// Returns `()` and has no side-effects.
237 fn allow<K>(_key: &K, _state: impl Into<StateSnapshot>) -> Self::PositiveOutcome {}
238
239 #[inline]
240 /// Returns the error indicating what
241 fn disallow<K>(
242 _key: &K,
243 state: impl Into<StateSnapshot>,
244 start_time: P,
245 ) -> Self::NegativeOutcome {
246 NotUntil::new(state.into(), start_time)
247 }
248}
249
250/// Middleware that returns the state of the rate limiter if a
251/// positive decision is reached.
252#[derive(Debug)]
253pub struct StateInformationMiddleware;
254
255impl<P: clock::Reference> RateLimitingMiddleware<P> for StateInformationMiddleware {
256 /// The state snapshot returned from the limiter.
257 type PositiveOutcome = StateSnapshot;
258
259 type NegativeOutcome = NotUntil<P>;
260
261 fn allow<K>(_key: &K, state: impl Into<StateSnapshot>) -> Self::PositiveOutcome {
262 state.into()
263 }
264
265 fn disallow<K>(
266 _key: &K,
267 state: impl Into<StateSnapshot>,
268 start_time: P,
269 ) -> Self::NegativeOutcome {
270 NotUntil::new(state.into(), start_time)
271 }
272}
273
274#[cfg(all(feature = "std", test))]
275mod test {
276 use std::time::Duration;
277
278 use super::*;
279
280 #[test]
281 fn middleware_impl_derives() {
282 assert_eq!(
283 format!("{StateInformationMiddleware:?}"),
284 "StateInformationMiddleware"
285 );
286 assert_eq!(
287 format!(
288 "{:?}",
289 NoOpMiddleware {
290 phantom: PhantomData::<Duration>,
291 }
292 ),
293 "NoOpMiddleware"
294 );
295 }
296}