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::connect::Connect;
14use hyper_util::client::legacy::Client;
15use tower_service::Service;
16
17use crate::future::RevProxyFuture;
18use crate::rewrite::PathRewriter;
19use crate::{client, Error};
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: self.client.clone(),
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#[expect(clippy::module_name_repetitions)]
198#[derive(Debug)]
199pub struct ReusedService<Pr, C, B = Incoming> {
200    client: Arc<Client<C, B>>,
201    scheme: Scheme,
202    authority: Authority,
203    path: Pr,
204}
205
206impl<Pr: Clone, C, B> Clone for ReusedService<Pr, C, B> {
207    #[inline]
208    fn clone(&self) -> Self {
209        Self {
210            client: self.client.clone(),
211            scheme: self.scheme.clone(),
212            authority: self.authority.clone(),
213            path: self.path.clone(),
214        }
215    }
216}
217
218impl<Pr, C, B> ReusedService<Pr, C, B> {
219    /// # Errors
220    ///
221    /// When `scheme` or `authority` cannot be converted into a [`Scheme`] or [`Authority`].
222    pub fn from<S, A>(
223        client: Arc<Client<C, B>>,
224        scheme: S,
225        authority: A,
226        path: Pr,
227    ) -> Result<Self, HttpError>
228    where
229        Scheme: TryFrom<S>,
230        <Scheme as TryFrom<S>>::Error: Into<HttpError>,
231        Authority: TryFrom<A>,
232        <Authority as TryFrom<A>>::Error: Into<HttpError>,
233    {
234        let scheme = scheme.try_into().map_err(Into::into)?;
235        let authority = authority.try_into().map_err(Into::into)?;
236        Ok(Self {
237            client,
238            scheme,
239            authority,
240            path,
241        })
242    }
243}
244
245impl<B, Pr> ReusedService<Pr, HttpConnector, B>
246where
247    B: HttpBody + Send,
248    B::Data: Send,
249{
250    /// # Errors
251    ///
252    /// When `authority` cannot be converted into an [`Authority`].
253    pub fn with_http_client<A>(
254        client: Arc<Client<HttpConnector, B>>,
255        authority: A,
256        path: Pr,
257    ) -> Result<Self, HttpError>
258    where
259        Authority: TryFrom<A>,
260        <Authority as TryFrom<A>>::Error: Into<HttpError>,
261    {
262        let authority = authority.try_into().map_err(Into::into)?;
263        Ok(Self {
264            client,
265            scheme: Scheme::HTTP,
266            authority,
267            path,
268        })
269    }
270}
271
272#[cfg(any(feature = "https", feature = "nativetls"))]
273impl<Pr, B> ReusedService<Pr, NativeTlsConnector<HttpConnector>, B>
274where
275    B: HttpBody + Send,
276    B::Data: Send,
277{
278    /// Alias to [`Self::with_nativetls_client()`].
279    ///
280    /// # Errors
281    ///
282    /// When `authority` cannot be converted into an [`Authority`].
283    #[cfg_attr(docsrs, doc(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#[cfg(feature = "nativetls")]
304impl<Pr, B> ReusedService<Pr, NativeTlsConnector<HttpConnector>, B>
305where
306    B: HttpBody + Send,
307    B::Data: Send,
308{
309    ///
310    /// # Errors
311    ///
312    /// When `authority` cannot be converted into an [`Authority`].
313    #[cfg_attr(docsrs, doc(cfg(feature = "nativetls")))]
314    pub fn with_nativetls_client<A>(
315        client: Arc<Client<NativeTlsConnector<HttpConnector>, B>>,
316        authority: A,
317        path: Pr,
318    ) -> Result<Self, HttpError>
319    where
320        Authority: TryFrom<A>,
321        <Authority as TryFrom<A>>::Error: Into<HttpError>,
322    {
323        let authority = authority.try_into().map_err(Into::into)?;
324        Ok(Self {
325            client,
326            scheme: Scheme::HTTPS,
327            authority,
328            path,
329        })
330    }
331}
332
333#[cfg(feature = "__rustls")]
334impl<Pr, B> ReusedService<Pr, RustlsConnector<HttpConnector>, B>
335where
336    B: HttpBody + Send,
337    B::Data: Send,
338{
339    ///
340    /// # Errors
341    ///
342    /// When `authority` cannot be converted into an [`Authority`].
343    #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
344    pub fn with_rustls_client<A>(
345        client: Arc<Client<RustlsConnector<HttpConnector>, B>>,
346        authority: A,
347        path: Pr,
348    ) -> Result<Self, HttpError>
349    where
350        Authority: TryFrom<A>,
351        <Authority as TryFrom<A>>::Error: Into<HttpError>,
352    {
353        let authority = authority.try_into().map_err(Into::into)?;
354        Ok(Self {
355            client,
356            scheme: Scheme::HTTPS,
357            authority,
358            path,
359        })
360    }
361}
362
363impl<C, B, Pr> Service<Request<B>> for ReusedService<Pr, C, B>
364where
365    C: Connect + Clone + Send + Sync + 'static,
366    B: HttpBody + Send + 'static + Unpin,
367    B::Data: Send,
368    B::Error: Into<BoxErr>,
369    Pr: PathRewriter,
370{
371    type Response = Result<Response<Incoming>, Error>;
372    type Error = Infallible;
373    type Future = RevProxyFuture;
374
375    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
376        Poll::Ready(Ok(()))
377    }
378
379    fn call(&mut self, req: Request<B>) -> Self::Future {
380        RevProxyFuture::new(
381            &self.client,
382            req,
383            &self.scheme,
384            &self.authority,
385            &mut self.path,
386        )
387    }
388}
389
390#[cfg(test)]
391mod test {
392    use http::uri::{Parts, Uri};
393    use mockito::ServerGuard;
394
395    use super::*;
396    use crate::{test_helper, ReplaceAll};
397
398    async fn make_svc() -> (
399        ServerGuard,
400        ReusedService<ReplaceAll<'static>, HttpConnector, String>,
401    ) {
402        let server = mockito::Server::new_async().await;
403        let uri = Uri::try_from(&server.url());
404        assert!(uri.is_ok());
405        let uri = uri.unwrap();
406
407        let Parts {
408            scheme, authority, ..
409        } = uri.into_parts();
410
411        let svc = ReusedService::from(
412            Arc::new(client::http_default()),
413            scheme.unwrap(),
414            authority.unwrap(),
415            ReplaceAll("foo", "goo"),
416        );
417        assert!(svc.is_ok());
418        (server, svc.unwrap())
419    }
420
421    #[tokio::test]
422    async fn match_path() {
423        let (mut server, mut svc) = make_svc().await;
424        test_helper::match_path(&mut server, &mut svc).await;
425    }
426
427    #[tokio::test]
428    async fn match_query() {
429        let (mut server, mut svc) = make_svc().await;
430        test_helper::match_query(&mut server, &mut svc).await;
431    }
432
433    #[tokio::test]
434    async fn match_post() {
435        let (mut server, mut svc) = make_svc().await;
436        test_helper::match_post(&mut server, &mut svc).await;
437    }
438
439    #[tokio::test]
440    async fn match_header() {
441        let (mut server, mut svc) = make_svc().await;
442        test_helper::match_header(&mut server, &mut svc).await;
443    }
444}