isahc/interceptor/mod.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 176 177 178 179 180
//! HTTP client interceptor API.
//!
//! This module provides the core types and functions for defining and working
//! with interceptors. Interceptors are handlers that augment HTTP client
//! functionality by decorating HTTP calls with custom logic.
//!
//! Known issues:
//!
//! - [`from_fn`] doesn't work as desired. The trait bounds are too ambiguous
//! for the compiler to infer for closures, and since the return type is
//! generic over a lifetime, there's no way to give the return type the
//! correct name using current Rust syntax.
//! - [`InterceptorObj`] wraps the returned future in an extra box.
//! - If an interceptor returns a custom error, it is stringified and wrapped in
//! `Error::Curl`. We should introduce a new error variant that boxes the
//! error and also records the type of the interceptor that created the error
//! for visibility. But we can't add a new variant right now without a BC
//! break. See [#182](https://github.com/sagebind/isahc/issues/182).
//! - Automatic redirect following currently bypasses interceptors for
//! subsequent requests. This will be fixed when redirect handling is
//! rewritten as an interceptor itself. See
//! [#232](https://github.com/sagebind/isahc/issues/232).
///
/// # Availability
///
/// This module is only available when the
/// [`unstable-interceptors`](../index.html#unstable-interceptors) feature is
/// enabled.
use crate::body::AsyncBody;
use http::{Request, Response};
use std::{error::Error, fmt, future::Future, pin::Pin};
mod context;
mod obj;
pub use self::context::Context;
pub(crate) use self::{context::Invoke, obj::InterceptorObj};
type InterceptorResult<E> = Result<Response<AsyncBody>, E>;
/// Defines an inline interceptor using a closure-like syntax.
///
/// Closures are not supported due to a limitation in Rust's type inference.
#[cfg(feature = "unstable-interceptors")]
#[macro_export]
macro_rules! interceptor {
($request:ident, $ctx:ident, $body:expr) => {{
async fn interceptor(
mut $request: $crate::http::Request<$crate::AsyncBody>,
$ctx: $crate::interceptor::Context<'_>,
) -> Result<$crate::http::Response<$crate::AsyncBody>, $crate::Error> {
(move || async move { $body })().await.map_err(Into::into)
}
$crate::interceptor::from_fn(interceptor)
}};
}
/// Base trait for interceptors.
///
/// Since clients may be used to send requests concurrently, all interceptors
/// must be synchronized and must be able to account for multiple requests being
/// made in parallel.
pub trait Interceptor: Send + Sync {
/// The type of error returned by this interceptor.
type Err: Error + Send + Sync + 'static;
/// Intercept a request, returning a response.
///
/// The returned future is allowed to borrow the interceptor for the
/// duration of its execution.
fn intercept<'a>(
&'a self,
request: Request<AsyncBody>,
ctx: Context<'a>,
) -> InterceptorFuture<'a, Self::Err>;
}
/// The type of future returned by an interceptor.
pub type InterceptorFuture<'a, E> = Pin<Box<dyn Future<Output = InterceptorResult<E>> + Send + 'a>>;
/// Creates an interceptor from an arbitrary closure or function.
pub fn from_fn<F, E>(f: F) -> InterceptorFn<F>
where
F: for<'a> private::AsyncFn2<Request<AsyncBody>, Context<'a>, Output = InterceptorResult<E>>
+ Send
+ Sync
+ 'static,
E: Error + Send + Sync + 'static,
{
InterceptorFn(f)
}
/// An interceptor created from an arbitrary closure or function. See
/// [`from_fn`] for details.
pub struct InterceptorFn<F>(F);
impl<E, F> Interceptor for InterceptorFn<F>
where
E: Error + Send + Sync + 'static,
F: for<'a> private::AsyncFn2<Request<AsyncBody>, Context<'a>, Output = InterceptorResult<E>>
+ Send
+ Sync
+ 'static,
{
type Err = E;
fn intercept<'a>(
&self,
request: Request<AsyncBody>,
ctx: Context<'a>,
) -> InterceptorFuture<'a, Self::Err> {
Box::pin(self.0.call(request, ctx))
}
}
impl<F: fmt::Debug> fmt::Debug for InterceptorFn<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
// Workaround for https://github.com/rust-lang/rust/issues/51004
#[allow(unreachable_pub)]
mod private {
use std::future::Future;
macro_rules! impl_async_fn {
($(($FnOnce:ident, $FnMut:ident, $Fn:ident, ($($arg:ident: $arg_ty:ident,)*)),)*) => {
$(
pub trait $FnOnce<$($arg_ty,)*> {
type Output;
type Future: Future<Output = Self::Output> + Send;
fn call_once(self, $($arg: $arg_ty,)*) -> Self::Future;
}
pub trait $FnMut<$($arg_ty,)*>: $FnOnce<$($arg_ty,)*> {
fn call_mut(&mut self, $($arg: $arg_ty,)*) -> Self::Future;
}
pub trait $Fn<$($arg_ty,)*>: $FnMut<$($arg_ty,)*> {
fn call(&self, $($arg: $arg_ty,)*) -> Self::Future;
}
impl<$($arg_ty,)* F, Fut> $FnOnce<$($arg_ty,)*> for F
where
F: FnOnce($($arg_ty,)*) -> Fut,
Fut: Future + Send,
{
type Output = Fut::Output;
type Future = Fut;
fn call_once(self, $($arg: $arg_ty,)*) -> Self::Future {
self($($arg,)*)
}
}
impl<$($arg_ty,)* F, Fut> $FnMut<$($arg_ty,)*> for F
where
F: FnMut($($arg_ty,)*) -> Fut,
Fut: Future + Send,
{
fn call_mut(&mut self, $($arg: $arg_ty,)*) -> Self::Future {
self($($arg,)*)
}
}
impl<$($arg_ty,)* F, Fut> $Fn<$($arg_ty,)*> for F
where
F: Fn($($arg_ty,)*) -> Fut,
Fut: Future + Send,
{
fn call(&self, $($arg: $arg_ty,)*) -> Self::Future {
self($($arg,)*)
}
}
)*
}
}
impl_async_fn! {
(AsyncFnOnce0, AsyncFnMut0, AsyncFn0, ()),
(AsyncFnOnce1, AsyncFnMut1, AsyncFn1, (a0:A0, )),
(AsyncFnOnce2, AsyncFnMut2, AsyncFn2, (a0:A0, a1:A1, )),
}
}