spin_factor_outbound_http/
intercept.rs

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