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#[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
61pub 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"))]
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#[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#[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
127pub 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#[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 #[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}