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#[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
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#[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 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 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 #[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 #[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 #[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}