Skip to main content

rama_http/layer/forwarded/
set_forwarded.rs

1use crate::Request;
2use crate::headers::HeaderMapExt;
3use crate::headers::forwarded::{
4    ForwardHeader, Via, XForwardedFor, XForwardedHost, XForwardedProto,
5};
6use rama_core::error::{BoxError, BoxErrorExt as _, ErrorContext as _};
7use rama_core::extensions::ExtensionsRef;
8use rama_core::{Layer, Service};
9use rama_http_headers::forwarded::Forwarded;
10use rama_net::address::Domain;
11use rama_net::forwarded::{ForwardedElement, NodeId};
12use rama_net::stream::SocketInfo;
13use rama_net::{AuthorityInputExt, Protocol, ProtocolInputExt};
14use std::fmt;
15use std::marker::PhantomData;
16
17/// Layer to write [`Forwarded`] information for this proxy,
18/// added to the end of the chain of forwarded information already known.
19///
20/// Existing wire headers are not parsed by this layer. Use the matching
21/// [`GetForwardedHeaderLayer`](super::GetForwardedHeaderLayer) earlier in the
22/// request path when an inbound chain should be preserved and extended.
23///
24/// This layer can set any header as long as you have a [`ForwardHeader`] implementation
25/// for the header you want to set. You can pass it as the type to the layer when creating
26/// the layer using [`SetForwardedHeaderLayer::new`].
27///
28/// The following headers are supported out of the box with each their own constructor:
29///
30/// - [`SetForwardedHeaderLayer::forwarded`]: the standard [`Forwarded`] header [`RFC 7239`](https://github.com/plabayo/rama/blob/main/rama-http-headers/specifications/rfc7239.txt);
31/// - [`SetForwardedHeaderLayer::via`]: the standard [`Via`] header from [RFC 9110](https://github.com/plabayo/rama/blob/main/rama-http-core/specifications/rfc9110.txt#section-7.6.3);
32/// - [`SetForwardedHeaderLayer::x_forwarded_for`]: the canonical [`X-Forwarded-For`][`XForwardedFor`] header (non-standard);
33/// - [`SetForwardedHeaderLayer::x_forwarded_host`]: the canonical [`X-Forwarded-Host`][`XForwardedHost`] header (non-standard);
34/// - [`SetForwardedHeaderLayer::x_forwarded_proto`]: the canonical [`X-Forwarded-Proto`][`XForwardedProto`] header (non-standard).
35///
36/// The "by" property is set to `rama` by default. Use [`SetForwardedHeaderLayer::set_forward_by`] to overwrite this,
37/// typically with the actual [`IPv4`]/[`IPv6`] address of your proxy.
38///
39/// [`IPv4`]: std::net::Ipv4Addr
40/// [`IPv6`]: std::net::Ipv6Addr
41///
42/// Rama also has the following headers already implemented for you to use:
43///
44/// > [`X-Real-Ip`], [`X-Client-Ip`], [`Client-Ip`], [`CF-Connecting-Ip`] and [`True-Client-Ip`].
45///
46/// There are no [`SetForwardedHeaderLayer`] constructors for these headers,
47/// but you can use the [`SetForwardedHeaderLayer::new`] constructor and pass the header type as a type parameter,
48/// alone or in a tuple with other headers.
49///
50/// [`X-Real-Ip`]: crate::headers::forwarded::XRealIp
51/// [`X-Client-Ip`]: crate::headers::forwarded::XClientIp
52/// [`Client-Ip`]: crate::headers::forwarded::ClientIp
53/// [`CF-Connecting-Ip`]: crate::headers::forwarded::CFConnectingIp
54/// [`True-Client-Ip`]: crate::headers::forwarded::TrueClientIp
55///
56/// ## Example
57///
58/// This example shows how you could expose the real Client IP using
59/// the [`X-Real-IP`][`crate::headers::forwarded::XRealIp`] header.
60///
61/// ```rust
62/// use rama_net::stream::SocketInfo;
63/// use rama_http::Request;
64/// use rama_core::service::service_fn;
65/// use rama_http::{headers::forwarded::XRealIp, layer::forwarded::SetForwardedHeaderLayer};
66/// use rama_core::{extensions::ExtensionsRef, Service, Layer};
67/// use std::convert::Infallible;
68///
69/// # type Body = ();
70/// # type State = ();
71///
72/// # #[tokio::main]
73/// # async fn main() {
74/// async fn svc(request: Request<Body>) -> Result<(), Infallible> {
75///     // ...
76///     # assert_eq!(
77///     #     request.headers().get("X-Real-Ip").unwrap(),
78///     #     "42.37.100.50:62345",
79///     # );
80///     # Ok(())
81/// }
82///
83/// let service = SetForwardedHeaderLayer::<XRealIp>::new()
84///     .into_layer(service_fn(svc));
85///
86/// # let mut req = Request::builder().uri("http://example.com").body(()).unwrap();
87/// # req.extensions().insert(SocketInfo::new(None, "42.37.100.50:62345".parse().unwrap()));
88/// service.serve(req).await.unwrap();
89/// # }
90/// ```
91pub struct SetForwardedHeaderLayer<T = Forwarded> {
92    by_node: NodeId,
93    _headers: PhantomData<fn() -> T>,
94}
95
96impl<T> fmt::Debug for SetForwardedHeaderLayer<T> {
97    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
98        f.debug_struct("SetForwardedHeaderLayer")
99            .field("by_node", &self.by_node)
100            .field(
101                "_headers",
102                &format_args!("{}", std::any::type_name::<fn() -> T>()),
103            )
104            .finish()
105    }
106}
107
108impl<T> Clone for SetForwardedHeaderLayer<T> {
109    fn clone(&self) -> Self {
110        Self {
111            by_node: self.by_node.clone(),
112            _headers: PhantomData,
113        }
114    }
115}
116
117impl<T> SetForwardedHeaderLayer<T> {
118    rama_utils::macros::generate_set_and_with! {
119        /// Set the given [`NodeId`] as the "by" property, identifying this proxy.
120        ///
121        /// Default of `None` will be set to `rama` otherwise.
122        pub fn forward_by(mut self, node_id: impl Into<NodeId>) -> Self {
123            self.by_node = node_id.into();
124            self
125        }
126    }
127}
128
129impl<T> SetForwardedHeaderLayer<T> {
130    /// Create a new `SetForwardedHeaderLayer` for the specified headers `T`.
131    #[must_use]
132    pub fn new() -> Self {
133        Self {
134            by_node: Domain::from_static("rama").into(),
135            _headers: PhantomData,
136        }
137    }
138}
139
140impl Default for SetForwardedHeaderLayer {
141    fn default() -> Self {
142        Self::forwarded()
143    }
144}
145
146impl SetForwardedHeaderLayer {
147    #[inline]
148    /// Create a new `SetForwardedHeaderLayer` for the standard [`Forwarded`] header.
149    #[must_use]
150    pub fn forwarded() -> Self {
151        Self::new()
152    }
153}
154
155impl SetForwardedHeaderLayer<Via> {
156    #[inline]
157    /// Create a new `SetForwardedHeaderLayer` for the canonical [`Via`] header.
158    #[must_use]
159    pub fn via() -> Self {
160        Self::new()
161    }
162}
163
164impl SetForwardedHeaderLayer<XForwardedFor> {
165    #[inline]
166    /// Create a new `SetForwardedHeaderLayer` for the canonical [`X-Forwarded-For`][XForwardedFor] header.
167    #[must_use]
168    pub fn x_forwarded_for() -> Self {
169        Self::new()
170    }
171}
172
173impl SetForwardedHeaderLayer<XForwardedHost> {
174    #[inline]
175    /// Create a new `SetForwardedHeaderLayer` for the canonical [`X-Forwarded-Host`][XForwardedHost] header.
176    #[must_use]
177    pub fn x_forwarded_host() -> Self {
178        Self::new()
179    }
180}
181
182impl SetForwardedHeaderLayer<XForwardedProto> {
183    #[inline]
184    /// Create a new `SetForwardedHeaderLayer` for the canonical [`X-Forwarded-Proto`][XForwardedProto] header.
185    #[must_use]
186    pub fn x_forwarded_proto() -> Self {
187        Self::new()
188    }
189}
190
191impl<H, S> Layer<S> for SetForwardedHeaderLayer<H> {
192    type Service = SetForwardedHeaderService<S, H>;
193
194    fn layer(&self, inner: S) -> Self::Service {
195        Self::Service {
196            inner,
197            by_node: self.by_node.clone(),
198            _headers: PhantomData,
199        }
200    }
201
202    fn into_layer(self, inner: S) -> Self::Service {
203        Self::Service {
204            inner,
205            by_node: self.by_node,
206            _headers: PhantomData,
207        }
208    }
209}
210
211/// Middleware [`Service`] to write [`Forwarded`] information for this proxy,
212/// added to the end of the chain of forwarded information already known.
213///
214/// See [`SetForwardedHeaderLayer`] for more information.
215pub struct SetForwardedHeaderService<S, T = Forwarded> {
216    inner: S,
217    by_node: NodeId,
218    _headers: PhantomData<fn() -> T>,
219}
220
221impl<S: fmt::Debug, T> fmt::Debug for SetForwardedHeaderService<S, T> {
222    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223        f.debug_struct("SetForwardedHeaderService")
224            .field("inner", &self.inner)
225            .field("by_node", &self.by_node)
226            .field(
227                "_headers",
228                &format_args!("{}", std::any::type_name::<fn() -> T>()),
229            )
230            .finish()
231    }
232}
233
234impl<S: Clone, T> Clone for SetForwardedHeaderService<S, T> {
235    fn clone(&self) -> Self {
236        Self {
237            inner: self.inner.clone(),
238            by_node: self.by_node.clone(),
239            _headers: PhantomData,
240        }
241    }
242}
243
244impl<S, T> SetForwardedHeaderService<S, T> {
245    rama_utils::macros::generate_set_and_with! {
246        /// Set the given [`NodeId`] as the "by" property, identifying this proxy.
247        ///
248        /// Default of `None` will be set to `rama` otherwise.
249        pub fn forward_by(mut self, node_id: impl Into<NodeId>) -> Self {
250            self.by_node = node_id.into();
251            self
252        }
253    }
254}
255
256impl<S, T> SetForwardedHeaderService<S, T> {
257    /// Create a new `SetForwardedHeaderService` for the specified headers `T`.
258    pub fn new(inner: S) -> Self {
259        Self {
260            inner,
261            by_node: Domain::from_static("rama").into(),
262            _headers: PhantomData,
263        }
264    }
265}
266
267impl<S> SetForwardedHeaderService<S> {
268    #[inline]
269    /// Create a new `SetForwardedHeaderService` for the standard [`Forwarded`] header.
270    pub fn forwarded(inner: S) -> Self {
271        Self::new(inner)
272    }
273}
274
275impl<S> SetForwardedHeaderService<S, Via> {
276    #[inline]
277    /// Create a new `SetForwardedHeaderService` for the canonical [`Via`] header.
278    pub fn via(inner: S) -> Self {
279        Self::new(inner)
280    }
281}
282
283impl<S> SetForwardedHeaderService<S, XForwardedFor> {
284    #[inline]
285    /// Create a new `SetForwardedHeaderService` for the canonical [`X-Forwarded-For`][XForwardedFor] header.
286    pub fn x_forwarded_for(inner: S) -> Self {
287        Self::new(inner)
288    }
289}
290
291impl<S> SetForwardedHeaderService<S, XForwardedHost> {
292    #[inline]
293    /// Create a new `SetForwardedHeaderService` for the canonical [`X-Forwarded-Host`][XForwardedHost] header.
294    pub fn x_forwarded_host(inner: S) -> Self {
295        Self::new(inner)
296    }
297}
298
299impl<S> SetForwardedHeaderService<S, XForwardedProto> {
300    #[inline]
301    /// Create a new `SetForwardedHeaderService` for the canonical [`X-Forwarded-Proto`][XForwardedProto] header.
302    pub fn x_forwarded_proto(inner: S) -> Self {
303        Self::new(inner)
304    }
305}
306
307impl<S, H, Body> Service<Request<Body>> for SetForwardedHeaderService<S, H>
308where
309    S: Service<Request<Body>, Error: Into<BoxError>>,
310    H: ForwardHeader + Send + Sync + 'static,
311    Body: Send + 'static,
312{
313    type Output = S::Output;
314    type Error = BoxError;
315
316    async fn serve(&self, mut req: Request<Body>) -> Result<Self::Output, Self::Error> {
317        let forwarded: Option<rama_net::forwarded::Forwarded> = req.extensions().get_ref().cloned();
318
319        let mut forwarded_element = ForwardedElement::new_forwarded_by(self.by_node.clone());
320
321        if let Some(peer_addr) = req
322            .extensions()
323            .get_ref::<SocketInfo>()
324            .map(|socket| socket.peer_addr())
325        {
326            forwarded_element.set_forwarded_for(peer_addr);
327        }
328        let authority = req
329            .authority()
330            .ok_or_else(|| BoxError::from_static_str("set forwarded: no authority"))?;
331
332        forwarded_element.set_forwarded_host(authority);
333
334        let protocol = req.protocol().unwrap_or(&Protocol::HTTP);
335        if let Ok(forwarded_proto) = protocol.try_into() {
336            forwarded_element.set_forwarded_proto(forwarded_proto);
337        }
338
339        let forwarded = match forwarded {
340            None => Some(rama_net::forwarded::Forwarded::new(forwarded_element)),
341            Some(mut forwarded) => {
342                forwarded.append(forwarded_element);
343                Some(forwarded)
344            }
345        };
346
347        if let Some(forwarded) = forwarded
348            && let Some(header) = H::try_from_forwarded(forwarded.iter())
349        {
350            req.headers_mut().typed_insert(header);
351        }
352
353        self.inner.serve(req).await.into_box_error()
354    }
355}
356
357#[cfg(test)]
358mod tests {
359    use super::*;
360    use crate::{
361        Response, StatusCode,
362        headers::forwarded::{TrueClientIp, XRealIp},
363        service::web::response::IntoResponse,
364    };
365    use rama_core::{Layer, error::BoxError, extensions::ExtensionsRef, service::service_fn};
366    use std::{convert::Infallible, net::IpAddr};
367
368    fn assert_is_service<T: Service<Request<()>>>(_: T) {}
369
370    async fn dummy_service_fn() -> Result<Response, BoxError> {
371        Ok(StatusCode::OK.into_response())
372    }
373
374    #[test]
375    fn test_set_forwarded_service_is_service() {
376        assert_is_service(SetForwardedHeaderService::forwarded(service_fn(
377            dummy_service_fn,
378        )));
379        assert_is_service(SetForwardedHeaderService::via(service_fn(dummy_service_fn)));
380        assert_is_service(SetForwardedHeaderService::x_forwarded_for(service_fn(
381            dummy_service_fn,
382        )));
383        assert_is_service(SetForwardedHeaderService::x_forwarded_proto(service_fn(
384            dummy_service_fn,
385        )));
386        assert_is_service(SetForwardedHeaderService::x_forwarded_host(service_fn(
387            dummy_service_fn,
388        )));
389        assert_is_service(SetForwardedHeaderService::<_, TrueClientIp>::new(
390            service_fn(dummy_service_fn),
391        ));
392        assert_is_service(SetForwardedHeaderLayer::via().into_layer(service_fn(dummy_service_fn)));
393        assert_is_service(
394            SetForwardedHeaderLayer::<XRealIp>::new().into_layer(service_fn(dummy_service_fn)),
395        );
396    }
397
398    #[tokio::test]
399    async fn test_set_forwarded_service_forwarded() {
400        async fn svc(request: Request<()>) -> Result<(), Infallible> {
401            assert_eq!(
402                request.headers().get("Forwarded").unwrap(),
403                "by=rama;host=\"example.com:80\";proto=http"
404            );
405            Ok(())
406        }
407
408        let service = SetForwardedHeaderService::forwarded(service_fn(svc));
409        let req = Request::builder()
410            .uri("http://example.com")
411            .body(())
412            .unwrap();
413        service.serve(req).await.unwrap();
414    }
415
416    #[tokio::test]
417    async fn test_set_forwarded_service_forwarded_with_chain() {
418        async fn svc(request: Request<()>) -> Result<(), Infallible> {
419            assert_eq!(
420                request.headers().get("Forwarded").unwrap(),
421                "for=12.23.34.45,by=rama;for=\"127.0.0.1:62345\";host=\"www.example.com:443\";proto=https",
422            );
423            Ok(())
424        }
425
426        let service = SetForwardedHeaderService::forwarded(service_fn(svc));
427        let req = Request::builder()
428            .uri("https://www.example.com")
429            .body(())
430            .unwrap();
431        req.extensions().insert(rama_net::forwarded::Forwarded::new(
432            ForwardedElement::new_forwarded_for(IpAddr::from([12, 23, 34, 45])),
433        ));
434        req.extensions()
435            .insert(SocketInfo::new(None, "127.0.0.1:62345".parse().unwrap()));
436        service.serve(req).await.unwrap();
437    }
438
439    #[tokio::test]
440    async fn test_set_forwarded_service_x_forwarded_for_with_chain() {
441        async fn svc(request: Request<()>) -> Result<(), Infallible> {
442            assert_eq!(
443                request.headers().get("X-Forwarded-For").unwrap(),
444                "12.23.34.45, 127.0.0.1",
445            );
446            Ok(())
447        }
448
449        let service = SetForwardedHeaderService::x_forwarded_for(service_fn(svc));
450        let req = Request::builder()
451            .uri("https://www.example.com")
452            .body(())
453            .unwrap();
454        req.extensions().insert(rama_net::forwarded::Forwarded::new(
455            ForwardedElement::new_forwarded_for(IpAddr::from([12, 23, 34, 45])),
456        ));
457        req.extensions()
458            .insert(SocketInfo::new(None, "127.0.0.1:62345".parse().unwrap()));
459        service.serve(req).await.unwrap();
460    }
461
462    #[tokio::test]
463    async fn test_set_forwarded_service_forwarded_fully_defined() {
464        async fn svc(request: Request<()>) -> Result<(), Infallible> {
465            assert_eq!(
466                request.headers().get("Forwarded").unwrap(),
467                "by=12.23.34.45;for=\"127.0.0.1:62345\";host=\"www.example.com:443\";proto=https",
468            );
469            Ok(())
470        }
471
472        let service = SetForwardedHeaderService::forwarded(service_fn(svc))
473            .with_forward_by(IpAddr::from([12, 23, 34, 45]));
474        let req = Request::builder()
475            .uri("https://www.example.com")
476            .body(())
477            .unwrap();
478        req.extensions()
479            .insert(SocketInfo::new(None, "127.0.0.1:62345".parse().unwrap()));
480        service.serve(req).await.unwrap();
481    }
482
483    #[tokio::test]
484    async fn test_set_forwarded_service_forwarded_fully_defined_with_chain() {
485        async fn svc(request: Request<()>) -> Result<(), Infallible> {
486            assert_eq!(
487                request.headers().get("Forwarded").unwrap(),
488                "by=rama;for=\"127.0.0.1:62345\";host=\"www.example.com:443\";proto=https",
489            );
490            Ok(())
491        }
492
493        let service = SetForwardedHeaderService::forwarded(service_fn(svc));
494        let req = Request::builder()
495            .uri("https://www.example.com")
496            .body(())
497            .unwrap();
498        req.extensions()
499            .insert(SocketInfo::new(None, "127.0.0.1:62345".parse().unwrap()));
500        service.serve(req).await.unwrap();
501    }
502}