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(any(feature = "https", 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    pub fn with_https_client<A>(
284        client: Arc<Client<NativeTlsConnector<HttpConnector>, B>>,
285        authority: A,
286        path: Pr,
287    ) -> Result<Self, HttpError>
288    where
289        Authority: TryFrom<A>,
290        <Authority as TryFrom<A>>::Error: Into<HttpError>,
291    {
292        let authority = authority.try_into().map_err(Into::into)?;
293        Ok(Self {
294            client,
295            scheme: Scheme::HTTPS,
296            authority,
297            path,
298        })
299    }
300}
301
302#[cfg(feature = "nativetls")]
303impl<Pr, B> ReusedService<Pr, NativeTlsConnector<HttpConnector>, B>
304where
305    B: HttpBody + Send,
306    B::Data: Send,
307{
308    ///
309    /// # Errors
310    ///
311    /// When `authority` cannot be converted into an [`Authority`].
312    #[cfg_attr(docsrs, doc(cfg(feature = "nativetls")))]
313    pub fn with_nativetls_client<A>(
314        client: Arc<Client<NativeTlsConnector<HttpConnector>, B>>,
315        authority: A,
316        path: Pr,
317    ) -> Result<Self, HttpError>
318    where
319        Authority: TryFrom<A>,
320        <Authority as TryFrom<A>>::Error: Into<HttpError>,
321    {
322        let authority = authority.try_into().map_err(Into::into)?;
323        Ok(Self {
324            client,
325            scheme: Scheme::HTTPS,
326            authority,
327            path,
328        })
329    }
330}
331
332#[cfg(feature = "__rustls")]
333impl<Pr, B> ReusedService<Pr, RustlsConnector<HttpConnector>, B>
334where
335    B: HttpBody + Send,
336    B::Data: Send,
337{
338    ///
339    /// # Errors
340    ///
341    /// When `authority` cannot be converted into an [`Authority`].
342    #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
343    pub fn with_rustls_client<A>(
344        client: Arc<Client<RustlsConnector<HttpConnector>, B>>,
345        authority: A,
346        path: Pr,
347    ) -> Result<Self, HttpError>
348    where
349        Authority: TryFrom<A>,
350        <Authority as TryFrom<A>>::Error: Into<HttpError>,
351    {
352        let authority = authority.try_into().map_err(Into::into)?;
353        Ok(Self {
354            client,
355            scheme: Scheme::HTTPS,
356            authority,
357            path,
358        })
359    }
360}
361
362impl<C, B, Pr> Service<Request<B>> for ReusedService<Pr, C, B>
363where
364    C: Connect + Clone + Send + Sync + 'static,
365    B: HttpBody + Send + 'static + Unpin,
366    B::Data: Send,
367    B::Error: Into<BoxErr>,
368    Pr: PathRewriter,
369{
370    type Response = Result<Response<Incoming>, ProxyError>;
371    type Error = Infallible;
372    type Future = RevProxyFuture;
373
374    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
375        Poll::Ready(Ok(()))
376    }
377
378    fn call(&mut self, req: Request<B>) -> Self::Future {
379        RevProxyFuture::new(
380            &self.client,
381            req,
382            &self.scheme,
383            &self.authority,
384            &mut self.path,
385        )
386    }
387}
388
389#[cfg(test)]
390mod test {
391    use http::uri::{Parts, Uri};
392    use mockito::ServerGuard;
393
394    use super::*;
395    use crate::{ReplaceAll, test_helper};
396
397    async fn make_svc() -> (
398        ServerGuard,
399        ReusedService<ReplaceAll<'static>, HttpConnector, String>,
400    ) {
401        let server = mockito::Server::new_async().await;
402        let uri = Uri::try_from(&server.url());
403        assert!(uri.is_ok());
404        let uri = uri.unwrap();
405
406        let Parts {
407            scheme, authority, ..
408        } = uri.into_parts();
409
410        let svc = ReusedService::from(
411            Arc::new(client::http_default()),
412            scheme.unwrap(),
413            authority.unwrap(),
414            ReplaceAll("foo", "goo"),
415        );
416        assert!(svc.is_ok());
417        (server, svc.unwrap())
418    }
419
420    #[tokio::test]
421    async fn match_path() {
422        let (mut server, mut svc) = make_svc().await;
423        test_helper::match_path(&mut server, &mut svc).await;
424    }
425
426    #[tokio::test]
427    async fn match_query() {
428        let (mut server, mut svc) = make_svc().await;
429        test_helper::match_query(&mut server, &mut svc).await;
430    }
431
432    #[tokio::test]
433    async fn match_post() {
434        let (mut server, mut svc) = make_svc().await;
435        test_helper::match_post(&mut server, &mut svc).await;
436    }
437
438    #[tokio::test]
439    async fn match_header() {
440        let (mut server, mut svc) = make_svc().await;
441        test_helper::match_header(&mut server, &mut svc).await;
442    }
443}