reverse_proxy_service/
reused.rs

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