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#[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
57pub 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#[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#[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#[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
139pub 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#[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 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 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 #[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 #[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 #[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}