twilight_http_ratelimiting/ticket.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
//! Flow for managing ratelimit tickets.
//!
//! Tickets are the [`Ratelimiter`]'s method of managing approval for a consumer
//! to be able to send a request.
//!
//! # Ratelimit Consumer
//!
//! ## 1. Requesting a ticket
//!
//! Consumers of a ratelimiter will call [`Ratelimiter::ticket`].
//!
//! ## 2. Waiting for approval
//!
//! In return consumers will receive a [`TicketReceiver`]. This must be polled
//! in order to know when the ratelimiter has approved a ticket.
//!
//! ## 3. Receiving approval
//!
//! When a ticket is approved and the future resolves, a [`TicketSender`] is
//! provided. This must be used to provide the ratelimiter with the response's
//! ratelimit headers.
//!
//! ## 4. Performing the request
//!
//! Consumers may now execute the HTTP request associated with the ticket. Once
//! a response (or lack of one) is received, the headers [must be parsed] and
//! sent to the ratelimiter via [`TicketSender::headers`]. This completes the
//! cycle.
//!
//! # Ratelimiter
//!
//! ## 1. Initializing a ticket's channels
//!
//! Ratelimiters will accept a request for a ticket when [`Ratelimiter::ticket`]
//! is called. You must call [`channel`] to create a channel between the
//! ratelimiter and the consumer.
//!
//! ## 2. Keeping the consumer waiting
//!
//! [`channel`] will return two halves: [`TicketNotifier`] and
//! [`TicketReceiver`]. Ratelimiters must keep the notifier and give the user
//! the receiver in return.
//!
//! ## 3. Notifying the consumer of ticket approval
//!
//! When any ratelimits have passed and a user is free to perform their request,
//! call [`TicketNotifier::available`]. If the user hasn't canceled their
//! request for a ticket, you will receive a [`TicketHeaders`].
//!
//! ## 4. Receiving the response's headers
//!
//! The consumer will perform their HTTP request and parse the response's
//! headers. Once the headers (or lack of headers) are available the user will
//! send them along the channel. Poll the provided [`TicketHeaders`] for those
//! headers to complete the cycle.
//!
//! [`Ratelimiter::ticket`]: super::Ratelimiter::ticket
//! [`Ratelimiter`]: super::Ratelimiter
//! [must be parsed]: super::headers
use crate::headers::RatelimitHeaders;
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
use tokio::sync::oneshot::{self, error::RecvError, Receiver, Sender};
/// Receiver to wait for the headers sent by the API consumer.
///
/// You must poll the future in order to process the headers. If the future
/// results to an error, then the API consumer dropped the sernding half of the
/// channel. You should treat this as if the request happened.
#[derive(Debug)]
pub struct TicketHeaders(Receiver<Option<RatelimitHeaders>>);
impl Future for TicketHeaders {
type Output = Result<Option<RatelimitHeaders>, RecvError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.0).poll(cx)
}
}
/// Indicate to the ratelimit consumer that their ticket has been granted and
/// they may now send a request.
#[derive(Debug)]
pub struct TicketNotifier(Sender<Sender<Option<RatelimitHeaders>>>);
impl TicketNotifier {
/// Signal to the ratelimiter consumer (an HTTP client) that a request may
/// now be performed.
///
/// A receiver is returned. This must be stored and awaited so that
/// ratelimiting backends can handle the headers that the API consumer will
/// send back, thus completing the cycle.
///
/// Returns a `None` if the consumer has dropped their
/// [`TicketReceiver`] half. The ticket is considered canceled.
#[must_use]
pub fn available(self) -> Option<TicketHeaders> {
let (tx, rx) = oneshot::channel();
self.0.send(tx).ok()?;
Some(TicketHeaders(rx))
}
}
/// Channel receiver to wait for availability of a ratelimit ticket.
///
/// This is used by the ratelimiter consumer (such as an API client) to wait for
/// an available ratelimit ticket.
///
/// Once one is available, a [`TicketSender`] will be produced which can be used to
/// send the associated HTTP response's ratelimit headers.
#[derive(Debug)]
pub struct TicketReceiver(Receiver<Sender<Option<RatelimitHeaders>>>);
impl Future for TicketReceiver {
type Output = Result<TicketSender, RecvError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut self.0).poll(cx).map_ok(TicketSender)
}
}
/// Channel sender to send response ratelimit information to the ratelimiter.
///
/// This is used by the ratelimiter consumer (such as an API client) once a
/// request has been granted via [`TicketReceiver`].
///
/// If a response results in available ratelimit headers, send them via
/// [`headers`] to the ratelimiter backend. If a response results in an
/// error - such as a server error or request cancellation - send `None`.
///
/// [`headers`]: Self::headers
#[derive(Debug)]
pub struct TicketSender(Sender<Option<RatelimitHeaders>>);
impl TicketSender {
/// Send the response's ratelimit headers to the ratelimiter.
///
/// This will allow the ratelimiter to complete the cycle and acknowledge
/// that the request has been completed. This must be done so that the
/// ratelimiter can process information such as whether there's a global
/// ratelimit.
///
/// # Errors
///
/// Returns the input headers if the ratelimiter has dropped the receiver
/// half. This may happen if the ratelimiter is dropped or if a timeout has
/// occurred.
pub fn headers(
self,
headers: Option<RatelimitHeaders>,
) -> Result<(), Option<RatelimitHeaders>> {
self.0.send(headers)
}
}
/// Produce a new channel consisting of a sender and receiver.
///
/// The notifier is to be used by the ratelimiter while the receiver is to be
/// provided to the consumer.
///
/// Refer to the [module-level] documentation for more information.
///
/// [module-level]: self
#[must_use]
pub fn channel() -> (TicketNotifier, TicketReceiver) {
let (tx, rx) = oneshot::channel();
(TicketNotifier(tx), TicketReceiver(rx))
}