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#[cfg_attr(
24 not(any(feature = "https", feature = "nativetls", feature = "__rustls")),
25 doc = "The return type of [`builder()`], [`builder_http()`]."
26)]
27#[cfg_attr(
28 all(
29 feature = "__rustls",
30 not(any(feature = "https", feature = "nativetls"))
31 ),
32 doc = "The return type of [`builder()`], [`builder_http()`] and [`builder_rustls()`]."
33)]
34#[cfg_attr(
35 all(
36 any(feature = "https", feature = "nativetls"),
37 not(feature = "__rustls")
38 ),
39 doc = "The return type of [`builder()`], [`builder_http()`], [`builder_https()`] and [`builder_nativetls()`]."
40)]
41#[cfg_attr(
42 all(any(feature = "https", feature = "nativetls"), feature = "__rustls"),
43 doc = "The return type of [`builder()`], [`builder_http()`], [`builder_https()`], [`builder_nativetls()`] and [`builder_rustls()`]."
44)]
45#[derive(Debug)]
46pub struct Builder<C = HttpConnector, B = Incoming> {
47 client: Arc<Client<C, B>>,
48 scheme: Scheme,
49 authority: Authority,
50}
51
52impl<C, B> Clone for Builder<C, B> {
53 fn clone(&self) -> Self {
54 Self {
55 client: Arc::clone(&self.client),
56 scheme: self.scheme.clone(),
57 authority: self.authority.clone(),
58 }
59 }
60}
61
62impl<C, B> Builder<C, B> {
63 pub fn build<Pr>(&self, path: Pr) -> ReusedService<Pr, C, B> {
64 let Self {
65 client,
66 scheme,
67 authority,
68 } = Clone::clone(self);
69 ReusedService {
70 client,
71 scheme,
72 authority,
73 path,
74 }
75 }
76}
77
78pub fn builder_http<B, A>(authority: A) -> Result<Builder<HttpConnector, B>, HttpError>
86where
87 B: HttpBody + Send,
88 B::Data: Send,
89 Authority: TryFrom<A>,
90 <Authority as TryFrom<A>>::Error: Into<HttpError>,
91{
92 builder(client::http_default(), Scheme::HTTP, authority)
93}
94
95#[cfg(any(feature = "https", feature = "nativetls"))]
105#[cfg_attr(docsrs, doc(cfg(any(feature = "https", feature = "nativetls"))))]
106pub fn builder_https<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::https_default(), Scheme::HTTPS, authority)
116}
117
118#[cfg(feature = "nativetls")]
126#[cfg_attr(docsrs, doc(cfg(feature = "nativetls")))]
127pub fn builder_nativetls<B, A>(
128 authority: A,
129) -> Result<Builder<NativeTlsConnector<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::nativetls_default(), Scheme::HTTPS, authority)
137}
138
139#[cfg(feature = "__rustls")]
147#[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
148pub fn builder_rustls<B, A>(
149 authority: A,
150) -> Result<Builder<RustlsConnector<HttpConnector>, B>, HttpError>
151where
152 B: HttpBody + Send,
153 B::Data: Send,
154 Authority: TryFrom<A>,
155 <Authority as TryFrom<A>>::Error: Into<HttpError>,
156{
157 builder(client::rustls_default(), Scheme::HTTPS, authority)
158}
159
160pub fn builder<C, B, S, A>(
169 client: Client<C, B>,
170 scheme: S,
171 authority: A,
172) -> Result<Builder<C, B>, HttpError>
173where
174 Scheme: TryFrom<S>,
175 <Scheme as TryFrom<S>>::Error: Into<HttpError>,
176 Authority: TryFrom<A>,
177 <Authority as TryFrom<A>>::Error: Into<HttpError>,
178{
179 let scheme = scheme.try_into().map_err(Into::into)?;
180 let authority = authority.try_into().map_err(Into::into)?;
181 Ok(Builder {
182 client: Arc::new(client),
183 scheme,
184 authority,
185 })
186}
187
188#[derive(Debug)]
219pub struct ReusedService<Pr, C, B = Incoming> {
220 client: Arc<Client<C, B>>,
221 scheme: Scheme,
222 authority: Authority,
223 path: Pr,
224}
225
226impl<Pr: Clone, C, B> Clone for ReusedService<Pr, C, B> {
227 #[inline]
228 fn clone(&self) -> Self {
229 Self {
230 client: Arc::clone(&self.client),
231 scheme: self.scheme.clone(),
232 authority: self.authority.clone(),
233 path: self.path.clone(),
234 }
235 }
236}
237
238impl<Pr, C, B> ReusedService<Pr, C, B> {
239 pub fn from<S, A>(
243 client: Arc<Client<C, B>>,
244 scheme: S,
245 authority: A,
246 path: Pr,
247 ) -> Result<Self, HttpError>
248 where
249 Scheme: TryFrom<S>,
250 <Scheme as TryFrom<S>>::Error: Into<HttpError>,
251 Authority: TryFrom<A>,
252 <Authority as TryFrom<A>>::Error: Into<HttpError>,
253 {
254 let scheme = scheme.try_into().map_err(Into::into)?;
255 let authority = authority.try_into().map_err(Into::into)?;
256 Ok(Self {
257 client,
258 scheme,
259 authority,
260 path,
261 })
262 }
263}
264
265impl<B, Pr> ReusedService<Pr, HttpConnector, B>
266where
267 B: HttpBody + Send,
268 B::Data: Send,
269{
270 pub fn with_http_client<A>(
274 client: Arc<Client<HttpConnector, B>>,
275 authority: A,
276 path: Pr,
277 ) -> Result<Self, HttpError>
278 where
279 Authority: TryFrom<A>,
280 <Authority as TryFrom<A>>::Error: Into<HttpError>,
281 {
282 let authority = authority.try_into().map_err(Into::into)?;
283 Ok(Self {
284 client,
285 scheme: Scheme::HTTP,
286 authority,
287 path,
288 })
289 }
290}
291
292#[cfg(feature = "nativetls")]
293impl<Pr, B> ReusedService<Pr, NativeTlsConnector<HttpConnector>, B>
294where
295 B: HttpBody + Send,
296 B::Data: Send,
297{
298 #[cfg_attr(docsrs, doc(cfg(any(feature = "https", feature = "nativetls"))))]
304 #[cfg(any(feature = "https", feature = "nativetls"))]
305 pub fn with_https_client<A>(
306 client: Arc<Client<NativeTlsConnector<HttpConnector>, B>>,
307 authority: A,
308 path: Pr,
309 ) -> Result<Self, HttpError>
310 where
311 Authority: TryFrom<A>,
312 <Authority as TryFrom<A>>::Error: Into<HttpError>,
313 {
314 let authority = authority.try_into().map_err(Into::into)?;
315 Ok(Self {
316 client,
317 scheme: Scheme::HTTPS,
318 authority,
319 path,
320 })
321 }
322
323 #[cfg_attr(docsrs, doc(cfg(feature = "nativetls")))]
328 #[cfg(feature = "nativetls")]
329 pub fn with_nativetls_client<A>(
330 client: Arc<Client<NativeTlsConnector<HttpConnector>, B>>,
331 authority: A,
332 path: Pr,
333 ) -> Result<Self, HttpError>
334 where
335 Authority: TryFrom<A>,
336 <Authority as TryFrom<A>>::Error: Into<HttpError>,
337 {
338 let authority = authority.try_into().map_err(Into::into)?;
339 Ok(Self {
340 client,
341 scheme: Scheme::HTTPS,
342 authority,
343 path,
344 })
345 }
346}
347
348#[cfg(feature = "__rustls")]
349impl<Pr, B> ReusedService<Pr, RustlsConnector<HttpConnector>, B>
350where
351 B: HttpBody + Send,
352 B::Data: Send,
353{
354 #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))]
359 pub fn with_rustls_client<A>(
360 client: Arc<Client<RustlsConnector<HttpConnector>, B>>,
361 authority: A,
362 path: Pr,
363 ) -> Result<Self, HttpError>
364 where
365 Authority: TryFrom<A>,
366 <Authority as TryFrom<A>>::Error: Into<HttpError>,
367 {
368 let authority = authority.try_into().map_err(Into::into)?;
369 Ok(Self {
370 client,
371 scheme: Scheme::HTTPS,
372 authority,
373 path,
374 })
375 }
376}
377
378impl<C, B, Pr> Service<Request<B>> for ReusedService<Pr, C, B>
379where
380 C: Connect + Clone + Send + Sync + 'static,
381 B: HttpBody + Send + 'static + Unpin,
382 B::Data: Send,
383 B::Error: Into<BoxErr>,
384 Pr: PathRewriter,
385{
386 type Response = Result<Response<Incoming>, ProxyError>;
387 type Error = Infallible;
388 type Future = RevProxyFuture;
389
390 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
391 Poll::Ready(Ok(()))
392 }
393
394 fn call(&mut self, req: Request<B>) -> Self::Future {
395 RevProxyFuture::new(
396 &self.client,
397 req,
398 &self.scheme,
399 &self.authority,
400 &mut self.path,
401 )
402 }
403}
404
405#[cfg(test)]
406mod test {
407 use http::uri::{Parts, Uri};
408 use mockito::ServerGuard;
409
410 use super::*;
411 use crate::{ReplaceAll, test_helper};
412
413 async fn make_svc() -> (
414 ServerGuard,
415 ReusedService<ReplaceAll<'static>, HttpConnector, String>,
416 ) {
417 let server = mockito::Server::new_async().await;
418 let uri = Uri::try_from(&server.url());
419 assert!(uri.is_ok());
420 let uri = uri.unwrap();
421
422 let Parts {
423 scheme, authority, ..
424 } = uri.into_parts();
425
426 let svc = ReusedService::from(
427 Arc::new(client::http_default()),
428 scheme.unwrap(),
429 authority.unwrap(),
430 ReplaceAll("foo", "goo"),
431 );
432 assert!(svc.is_ok());
433 (server, svc.unwrap())
434 }
435
436 #[tokio::test]
437 async fn match_path() {
438 let (mut server, mut svc) = make_svc().await;
439 test_helper::match_path(&mut server, &mut svc).await;
440 }
441
442 #[tokio::test]
443 async fn match_query() {
444 let (mut server, mut svc) = make_svc().await;
445 test_helper::match_query(&mut server, &mut svc).await;
446 }
447
448 #[tokio::test]
449 async fn match_post() {
450 let (mut server, mut svc) = make_svc().await;
451 test_helper::match_post(&mut server, &mut svc).await;
452 }
453
454 #[tokio::test]
455 async fn match_header() {
456 let (mut server, mut svc) = make_svc().await;
457 test_helper::match_header(&mut server, &mut svc).await;
458 }
459}