spin_factor_outbound_http/
intercept.rs

1use http::{Request, Response};
2use http_body_util::{BodyExt, Full};
3use spin_world::async_trait;
4use wasmtime_wasi_http::{body::HyperOutgoingBody, HttpResult};
5
6pub type HyperBody = HyperOutgoingBody;
7
8/// An outbound HTTP request interceptor to be used with
9/// [`super::InstanceState::set_request_interceptor`].
10#[async_trait]
11pub trait OutboundHttpInterceptor: Send + Sync {
12    /// Intercept an outgoing HTTP request.
13    ///
14    /// If this method returns [`InterceptOutcome::Continue`], the (possibly
15    /// updated) request will be passed on to the default outgoing request
16    /// handler.
17    ///
18    /// If this method returns [`InterceptOutcome::Complete`], the inner result
19    /// will be returned as the result of the request, bypassing the default
20    /// handler. The `request` will also be dropped immediately.
21    async fn intercept(&self, request: InterceptRequest) -> HttpResult<InterceptOutcome>;
22}
23
24/// The type returned by an [`OutboundHttpInterceptor`].
25pub enum InterceptOutcome {
26    /// The intercepted request will be passed on to the default outgoing
27    /// request handler.
28    Continue(InterceptRequest),
29    /// The given response will be returned as the result of the intercepted
30    /// request, bypassing the default handler.
31    Complete(Response<HyperBody>),
32}
33
34/// An intercepted outgoing HTTP request.
35///
36/// This is a wrapper that implements `DerefMut<Target = Request<()>>` for
37/// inspection and modification of the request envelope. If the body needs to be
38/// consumed, call [`Self::into_hyper_request`].
39pub struct InterceptRequest {
40    inner: Request<()>,
41    body: InterceptBody,
42    pub(crate) override_connect_host: Option<String>,
43}
44
45enum InterceptBody {
46    Hyper(HyperBody),
47    Vec(Vec<u8>),
48}
49
50impl InterceptRequest {
51    /// Overrides the host that will be connected to for this outbound request.
52    ///
53    /// This override does not have any effect on TLS server name checking or
54    /// HTTP authority / host headers.
55    ///
56    /// This host will not be checked against `allowed_outbound_hosts`; if that
57    /// check should occur it must be performed by the interceptor. The resolved
58    /// IP addresses from this host will be checked against blocked IP networks.
59    pub fn override_connect_host(&mut self, host: impl Into<String>) {
60        self.override_connect_host = Some(host.into())
61    }
62
63    pub fn into_hyper_request(self) -> Request<HyperBody> {
64        let (parts, ()) = self.inner.into_parts();
65        Request::from_parts(parts, self.body.into())
66    }
67
68    pub(crate) fn into_vec_request(self) -> Option<Request<Vec<u8>>> {
69        let InterceptBody::Vec(bytes) = self.body else {
70            return None;
71        };
72        let (parts, ()) = self.inner.into_parts();
73        Some(Request::from_parts(parts, bytes))
74    }
75}
76
77impl std::ops::Deref for InterceptRequest {
78    type Target = Request<()>;
79
80    fn deref(&self) -> &Self::Target {
81        &self.inner
82    }
83}
84
85impl std::ops::DerefMut for InterceptRequest {
86    fn deref_mut(&mut self) -> &mut Self::Target {
87        &mut self.inner
88    }
89}
90
91impl From<Request<HyperBody>> for InterceptRequest {
92    fn from(req: Request<HyperBody>) -> Self {
93        let (parts, body) = req.into_parts();
94        Self {
95            inner: Request::from_parts(parts, ()),
96            body: InterceptBody::Hyper(body),
97            override_connect_host: None,
98        }
99    }
100}
101
102impl From<Request<Vec<u8>>> for InterceptRequest {
103    fn from(req: Request<Vec<u8>>) -> Self {
104        let (parts, body) = req.into_parts();
105        Self {
106            inner: Request::from_parts(parts, ()),
107            body: InterceptBody::Vec(body),
108            override_connect_host: None,
109        }
110    }
111}
112
113impl From<InterceptBody> for HyperBody {
114    fn from(body: InterceptBody) -> Self {
115        match body {
116            InterceptBody::Hyper(body) => body,
117            InterceptBody::Vec(bytes) => {
118                Full::new(bytes.into()).map_err(|err| match err {}).boxed()
119            }
120        }
121    }
122}