Skip to main content

axum_proxy/
reused.rs

1use std::convert::Infallible;
2use std::sync::Arc;
3use std::task::{Context, Poll};
4
5use client::HttpConnector;
6#[cfg(feature = "__rustls")]
7use client::RustlsConnector;
8use http::uri::{Authority, Scheme};
9use http::{Error as HttpError, Request, Response};
10use hyper::body::{Body as HttpBody, Incoming};
11#[cfg(feature = "nativetls")]
12use hyper_tls::HttpsConnector as NativeTlsConnector;
13use hyper_util::client::legacy::Client;
14use hyper_util::client::legacy::connect::Connect;
15use tower_service::Service;
16
17use crate::future::RevProxyFuture;
18use crate::rewrite::PathRewriter;
19use crate::{ProxyError, client};
20
21type BoxErr = Box<dyn std::error::Error + Send + Sync>;
22
23/// The return type of [`builder()`], [`builder_http()`] and [`builder_https()`].
24#[derive(Debug)]
25pub struct Builder<C = HttpConnector, B = Incoming> {
26    client: Arc<Client<C, B>>,
27    scheme: Scheme,
28    authority: Authority,
29}
30
31impl<C, B> Clone for Builder<C, B> {
32    fn clone(&self) -> Self {
33        Self {
34            client: Arc::clone(&self.client),
35            scheme: self.scheme.clone(),
36            authority: self.authority.clone(),
37        }
38    }
39}
40
41impl<C, B> Builder<C, B> {
42    pub fn build<Pr>(&self, path: Pr) -> ReusedService<Pr, C, B> {
43        let Self {
44            client,
45            scheme,
46            authority,
47        } = Clone::clone(self);
48        ReusedService {
49            client,
50            scheme,
51            authority,
52            path,
53        }
54    }
55}
56
57/// Builder of [`ReusedService`], with [`client::http_default()`].
58///
59/// For the meaning of "authority", refer to the documentation of [`Uri`](http::uri::Uri).
60///
61/// # Errors
62///
63/// When `authority` cannot be converted into an [`Authority`].
64pub fn builder_http<B, A>(authority: A) -> Result<Builder<HttpConnector, B>, HttpError>
65where
66    B: HttpBody + Send,
67    B::Data: Send,
68    Authority: TryFrom<A>,
69    <Authority as TryFrom<A>>::Error: Into<HttpError>,
70{
71    builder(client::http_default(), Scheme::HTTP, authority)
72}
73
74/// Builder of [`ReusedService`], with [`client::https_default()`].
75///
76/// This is the same as [`builder_nativetls()`].
77///
78/// For the meaning of "authority", refer to the documentation of [`Uri`](http::uri::Uri).
79///
80/// # Errors
81///
82/// When `authority` cannot be converted into an [`Authority`].
83#[cfg(any(feature = "https", feature = "nativetls"))]
84#[cfg_attr(docsrs, doc(cfg(any(feature = "https", feature = "nativetls"))))]
85pub fn builder_https<B, A>(
86    authority: A,
87) -> Result<Builder<NativeTlsConnector<HttpConnector>, B>, HttpError>
88where
89    B: HttpBody + Send,
90    B::Data: Send,
91    Authority: TryFrom<A>,
92    <Authority as TryFrom<A>>::Error: Into<HttpError>,
93{
94    builder(client::https_default(), Scheme::HTTPS, authority)
95}
96
97/// Builder of [`ReusedService`], with [`client::nativetls_default()`].
98///
99/// For the meaning of "authority", refer to the documentation of [`Uri`](http::uri::Uri).
100///
101/// # Errors
102///
103/// When `authority` cannot be converted into an [`Authority`].
104#[cfg(feature = "nativetls")]
105#[cfg_attr(docsrs, doc(cfg(feature = "nativetls")))]
106pub fn builder_nativetls<B, A>(
107    authority: A,
108) -> Result<Builder<NativeTlsConnector<HttpConnector>, B>, HttpError>
109where
110    B: HttpBody + Send,
111    B::Data: Send,
112    Authority: TryFrom<A>,
113    <Authority as TryFrom<A>>::Error: Into<HttpError>,
114{
115    builder(client::nativetls_default(), Scheme::HTTPS, authority)
116}
117
118/// Builder of [`ReusedService`], with [`client::rustls_default()`].
119///
120/// For the meaning of "authority", refer to the documentation of [`Uri`](http::uri::Uri).
121///
122/// # Errors
123///
124/// When `authority` cannot be converted into an [`Authority`].
125#[cfg(feature = "__rustls")]
126#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
127pub fn builder_rustls<B, A>(
128    authority: A,
129) -> Result<Builder<RustlsConnector<HttpConnector>, B>, HttpError>
130where
131    B: HttpBody + Send,
132    B::Data: Send,
133    Authority: TryFrom<A>,
134    <Authority as TryFrom<A>>::Error: Into<HttpError>,
135{
136    builder(client::rustls_default(), Scheme::HTTPS, authority)
137}
138
139/// Builder of [`ReusedService`].
140///
141/// For the meaning of "scheme" and "authority", refer to the documentation of
142/// [`Uri`](http::uri::Uri).
143///
144/// # Errors
145///
146/// When `scheme` or `authority` cannot be converted into a [`Scheme`] or [`Authority`].
147pub fn builder<C, B, S, A>(
148    client: Client<C, B>,
149    scheme: S,
150    authority: A,
151) -> Result<Builder<C, B>, HttpError>
152where
153    Scheme: TryFrom<S>,
154    <Scheme as TryFrom<S>>::Error: Into<HttpError>,
155    Authority: TryFrom<A>,
156    <Authority as TryFrom<A>>::Error: Into<HttpError>,
157{
158    let scheme = scheme.try_into().map_err(Into::into)?;
159    let authority = authority.try_into().map_err(Into::into)?;
160    Ok(Builder {
161        client: Arc::new(client),
162        scheme,
163        authority,
164    })
165}
166
167/// A [`Service<Request<B>>`] that sends a request and returns the response, sharing a [`Client`].
168///
169/// ```
170/// # async fn run_test() {
171/// # use axum_proxy::ReusedService;
172/// # use axum_proxy::Static;
173/// # use tower_service::Service;
174/// # use http_body_util::Empty;
175/// # use http::Request;
176/// # use hyper::body::{Body, Bytes};
177/// let svc_builder = axum_proxy::builder_http("example.com:1234").unwrap();
178///
179/// let mut svc1 = svc_builder.build(Static("bar"));
180/// let mut svc2 = svc_builder.build(Static("baz"));
181///
182/// let req = Request::builder()
183///     .uri("https://myserver.com/foo")
184///     .body(Empty::<Bytes>::new())
185///     .unwrap();
186/// // http://example.com:1234/bar
187/// let _res = svc1.call(req).await.unwrap();
188///
189/// let req = Request::builder()
190///     .uri("https://myserver.com/foo")
191///     .body(Empty::<Bytes>::new())
192///     .unwrap();
193/// // http://example.com:1234/baz
194/// let _res = svc2.call(req).await.unwrap();
195/// # }
196/// ```
197#[derive(Debug)]
198pub struct ReusedService<Pr, C, B = Incoming> {
199    client: Arc<Client<C, B>>,
200    scheme: Scheme,
201    authority: Authority,
202    path: Pr,
203}
204
205impl<Pr: Clone, C, B> Clone for ReusedService<Pr, C, B> {
206    #[inline]
207    fn clone(&self) -> Self {
208        Self {
209            client: Arc::clone(&self.client),
210            scheme: self.scheme.clone(),
211            authority: self.authority.clone(),
212            path: self.path.clone(),
213        }
214    }
215}
216
217impl<Pr, C, B> ReusedService<Pr, C, B> {
218    /// # Errors
219    ///
220    /// When `scheme` or `authority` cannot be converted into a [`Scheme`] or [`Authority`].
221    pub fn from<S, A>(
222        client: Arc<Client<C, B>>,
223        scheme: S,
224        authority: A,
225        path: Pr,
226    ) -> Result<Self, HttpError>
227    where
228        Scheme: TryFrom<S>,
229        <Scheme as TryFrom<S>>::Error: Into<HttpError>,
230        Authority: TryFrom<A>,
231        <Authority as TryFrom<A>>::Error: Into<HttpError>,
232    {
233        let scheme = scheme.try_into().map_err(Into::into)?;
234        let authority = authority.try_into().map_err(Into::into)?;
235        Ok(Self {
236            client,
237            scheme,
238            authority,
239            path,
240        })
241    }
242}
243
244impl<B, Pr> ReusedService<Pr, HttpConnector, B>
245where
246    B: HttpBody + Send,
247    B::Data: Send,
248{
249    /// # Errors
250    ///
251    /// When `authority` cannot be converted into an [`Authority`].
252    pub fn with_http_client<A>(
253        client: Arc<Client<HttpConnector, B>>,
254        authority: A,
255        path: Pr,
256    ) -> Result<Self, HttpError>
257    where
258        Authority: TryFrom<A>,
259        <Authority as TryFrom<A>>::Error: Into<HttpError>,
260    {
261        let authority = authority.try_into().map_err(Into::into)?;
262        Ok(Self {
263            client,
264            scheme: Scheme::HTTP,
265            authority,
266            path,
267        })
268    }
269}
270
271#[cfg(feature = "nativetls")]
272impl<Pr, B> ReusedService<Pr, NativeTlsConnector<HttpConnector>, B>
273where
274    B: HttpBody + Send,
275    B::Data: Send,
276{
277    /// Alias to [`Self::with_nativetls_client()`].
278    ///
279    /// # Errors
280    ///
281    /// When `authority` cannot be converted into an [`Authority`].
282    #[cfg_attr(docsrs, doc(cfg(any(feature = "https", feature = "nativetls"))))]
283    #[cfg(any(feature = "https", feature = "nativetls"))]
284    pub fn with_https_client<A>(
285        client: Arc<Client<NativeTlsConnector<HttpConnector>, B>>,
286        authority: A,
287        path: Pr,
288    ) -> Result<Self, HttpError>
289    where
290        Authority: TryFrom<A>,
291        <Authority as TryFrom<A>>::Error: Into<HttpError>,
292    {
293        let authority = authority.try_into().map_err(Into::into)?;
294        Ok(Self {
295            client,
296            scheme: Scheme::HTTPS,
297            authority,
298            path,
299        })
300    }
301
302    ///
303    /// # Errors
304    ///
305    /// When `authority` cannot be converted into an [`Authority`].
306    #[cfg_attr(docsrs, doc(cfg(feature = "nativetls")))]
307    #[cfg(feature = "nativetls")]
308    pub fn with_nativetls_client<A>(
309        client: Arc<Client<NativeTlsConnector<HttpConnector>, B>>,
310        authority: A,
311        path: Pr,
312    ) -> Result<Self, HttpError>
313    where
314        Authority: TryFrom<A>,
315        <Authority as TryFrom<A>>::Error: Into<HttpError>,
316    {
317        let authority = authority.try_into().map_err(Into::into)?;
318        Ok(Self {
319            client,
320            scheme: Scheme::HTTPS,
321            authority,
322            path,
323        })
324    }
325}
326
327#[cfg(feature = "__rustls")]
328impl<Pr, B> ReusedService<Pr, RustlsConnector<HttpConnector>, B>
329where
330    B: HttpBody + Send,
331    B::Data: Send,
332{
333    ///
334    /// # Errors
335    ///
336    /// When `authority` cannot be converted into an [`Authority`].
337    #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
338    pub fn with_rustls_client<A>(
339        client: Arc<Client<RustlsConnector<HttpConnector>, B>>,
340        authority: A,
341        path: Pr,
342    ) -> Result<Self, HttpError>
343    where
344        Authority: TryFrom<A>,
345        <Authority as TryFrom<A>>::Error: Into<HttpError>,
346    {
347        let authority = authority.try_into().map_err(Into::into)?;
348        Ok(Self {
349            client,
350            scheme: Scheme::HTTPS,
351            authority,
352            path,
353        })
354    }
355}
356
357impl<C, B, Pr> Service<Request<B>> for ReusedService<Pr, C, B>
358where
359    C: Connect + Clone + Send + Sync + 'static,
360    B: HttpBody + Send + 'static + Unpin,
361    B::Data: Send,
362    B::Error: Into<BoxErr>,
363    Pr: PathRewriter,
364{
365    type Response = Result<Response<Incoming>, ProxyError>;
366    type Error = Infallible;
367    type Future = RevProxyFuture;
368
369    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
370        Poll::Ready(Ok(()))
371    }
372
373    fn call(&mut self, req: Request<B>) -> Self::Future {
374        RevProxyFuture::new(
375            &self.client,
376            req,
377            &self.scheme,
378            &self.authority,
379            &mut self.path,
380        )
381    }
382}
383
384#[cfg(test)]
385mod test {
386    use http::uri::{Parts, Uri};
387    use mockito::ServerGuard;
388
389    use super::*;
390    use crate::{ReplaceAll, test_helper};
391
392    async fn make_svc() -> (
393        ServerGuard,
394        ReusedService<ReplaceAll<'static>, HttpConnector, String>,
395    ) {
396        let server = mockito::Server::new_async().await;
397        let uri = Uri::try_from(&server.url());
398        assert!(uri.is_ok());
399        let uri = uri.unwrap();
400
401        let Parts {
402            scheme, authority, ..
403        } = uri.into_parts();
404
405        let svc = ReusedService::from(
406            Arc::new(client::http_default()),
407            scheme.unwrap(),
408            authority.unwrap(),
409            ReplaceAll("foo", "goo"),
410        );
411        assert!(svc.is_ok());
412        (server, svc.unwrap())
413    }
414
415    #[tokio::test]
416    async fn match_path() {
417        let (mut server, mut svc) = make_svc().await;
418        test_helper::match_path(&mut server, &mut svc).await;
419    }
420
421    #[tokio::test]
422    async fn match_query() {
423        let (mut server, mut svc) = make_svc().await;
424        test_helper::match_query(&mut server, &mut svc).await;
425    }
426
427    #[tokio::test]
428    async fn match_post() {
429        let (mut server, mut svc) = make_svc().await;
430        test_helper::match_post(&mut server, &mut svc).await;
431    }
432
433    #[tokio::test]
434    async fn match_header() {
435        let (mut server, mut svc) = make_svc().await;
436        test_helper::match_header(&mut server, &mut svc).await;
437    }
438}