1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3#,"
11)]
12#, [`HttpsConnector`](hyper_tls::HttpsConnector),"
18)]
19#, [`client::RustlsConnector`],"
25)]
26#, [`HttpsConnector`](hyper_tls::HttpsConnector), [`client::RustlsConnector`],"
29)]
30# accepts only such `Service`s."
120)]
121#![cfg_attr(
123 feature = "axum",
124 doc = "The [`ProxyError`] type implements [`IntoResponse`](axum::response::IntoResponse) if you enable the \
125 `axum` feature. \
126 It returns an empty body, with the status code `INTERNAL_SERVER_ERROR`. The description of this \
127 error will be logged out with [`tracing::event!`] at the [`tracing::Level::ERROR`] level in the \
128 [`IntoResponse::into_response`](axum::response::IntoResponse::into_response) method. \
129"
130)]
131# for [`ProxyError`]"
148)]
149mod error;
156pub use error::ProxyError;
157
158#[cfg(any(feature = "http1", feature = "http2"))]
159#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
160pub mod client;
161
162pub mod rewrite;
163pub use rewrite::*;
164
165mod future;
166pub use future::RevProxyFuture;
167
168#[cfg(any(feature = "http1", feature = "http2"))]
169mod oneshot;
170#[cfg(any(feature = "http1", feature = "http2"))]
171#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
172pub use oneshot::OneshotService;
173
174#[cfg(any(feature = "http1", feature = "http2"))]
175mod reused;
176#[cfg(any(feature = "http1", feature = "http2"))]
177#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
178pub use reused::Builder as ReusedServiceBuilder;
179#[cfg(any(feature = "http1", feature = "http2"))]
180#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
181pub use reused::ReusedService;
182#[cfg(all(
183 any(feature = "http1", feature = "http2"),
184 any(feature = "https", feature = "nativetls")
185))]
186#[cfg_attr(
187 docsrs,
188 doc(cfg(all(
189 any(feature = "http1", feature = "http2"),
190 any(feature = "https", feature = "nativetls")
191 )))
192)]
193pub use reused::builder_https;
194#[cfg(all(any(feature = "http1", feature = "http2"), feature = "nativetls"))]
195#[cfg_attr(
196 docsrs,
197 doc(cfg(all(any(feature = "http1", feature = "http2"), feature = "nativetls")))
198)]
199pub use reused::builder_nativetls;
200#[cfg(all(any(feature = "http1", feature = "http2"), feature = "__rustls"))]
201#[cfg_attr(
202 docsrs,
203 doc(cfg(all(any(feature = "http1", feature = "http2"), feature = "rustls")))
204)]
205pub use reused::builder_rustls;
206#[cfg(any(feature = "http1", feature = "http2"))]
207#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
208pub use reused::{builder, builder_http};
209
210#[cfg(not(feature = "http1"))]
211compile_error!("http1 is a mandatory feature");
212
213#[cfg(all(
214 any(feature = "rustls-ring", feature = "rustls-aws-lc"),
215 not(any(feature = "rustls-webpki-roots", feature = "rustls-native-roots"))
216))]
217compile_error!(
218 "When enabling rustls-ring and/or rustls-aws-lc, you must enable rustls-webpki-roots and/or rustls-native-roots"
219);
220
221#[cfg(test)]
222mod test_helper {
223 use std::convert::Infallible;
224
225 use http::{Request, Response, StatusCode, Version};
226 use http_body_util::BodyExt as _;
227 use hyper::body::Incoming;
228 use mockito::{Matcher, ServerGuard};
229 use pretty_assertions::assert_eq;
230 use tower_service::Service;
231
232 use super::{ProxyError, RevProxyFuture};
233
234 async fn call<S, B>(
235 service: &mut S,
236 (method, suffix, content_type, body): (&str, &str, Option<&str>, B),
237 expected: (StatusCode, &str),
238 ) where
239 S: Service<
240 Request<String>,
241 Response = Result<Response<Incoming>, ProxyError>,
242 Error = Infallible,
243 Future = RevProxyFuture,
244 >,
245 B: Into<String>,
246 {
247 let mut builder = Request::builder()
248 .method(method)
249 .uri(format!("https://example.com{}", suffix));
250
251 if let Some(content_type) = content_type {
252 builder = builder.header("Content-Type", content_type);
253 }
254
255 let request = builder.body(body.into()).unwrap();
256
257 let result = service.call(request).await.unwrap();
258 assert!(result.is_ok());
259
260 let response = result.unwrap();
261 assert_eq!(response.status(), expected.0);
262
263 let body = response.into_body().collect().await;
264 assert!(body.is_ok());
265
266 assert_eq!(body.unwrap().to_bytes(), expected.1);
267 }
268
269 pub async fn match_path<S>(server: &mut ServerGuard, svc: &mut S)
270 where
271 S: Service<
272 Request<String>,
273 Response = Result<Response<Incoming>, ProxyError>,
274 Error = Infallible,
275 Future = RevProxyFuture,
276 >,
277 {
278 let _mk = server
279 .mock("GET", "/goo/bar/goo/baz/goo")
280 .with_body("ok")
281 .create_async()
282 .await;
283
284 call(
285 svc,
286 ("GET", "/foo/bar/foo/baz/foo", None, ""),
287 (StatusCode::OK, "ok"),
288 )
289 .await;
290
291 call(
292 svc,
293 ("GET", "/foo/bar/foo/baz", None, ""),
294 (StatusCode::NOT_IMPLEMENTED, ""),
295 )
296 .await;
297 }
298
299 pub async fn downgrade_version<S>(server: &mut ServerGuard, svc: &mut S)
300 where
301 S: Service<
302 Request<String>,
303 Response = Result<Response<Incoming>, ProxyError>,
304 Error = Infallible,
305 Future = RevProxyFuture,
306 >,
307 {
308 let _mk = server
309 .mock("GET", "/goo")
310 .with_body("ok")
311 .create_async()
312 .await;
313
314 let request = Request::builder()
315 .method("GET")
316 .uri("https://example.com/foo")
317 .version(Version::HTTP_2)
318 .body(String::new())
319 .unwrap();
320
321 let response = svc.call(request).await.unwrap().unwrap();
322 assert_eq!(response.status(), StatusCode::OK);
323
324 let body = response.into_body().collect().await.unwrap();
325 assert_eq!(body.to_bytes(), "ok");
326 }
327
328 pub async fn match_query<S>(server: &mut ServerGuard, svc: &mut S)
329 where
330 S: Service<
331 Request<String>,
332 Response = Result<Response<Incoming>, ProxyError>,
333 Error = Infallible,
334 Future = RevProxyFuture,
335 >,
336 {
337 let _mk = server
338 .mock("GET", "/goo")
339 .match_query(Matcher::UrlEncoded("greeting".into(), "good day".into()))
340 .with_body("ok")
341 .create_async()
342 .await;
343
344 call(
345 svc,
346 ("GET", "/foo?greeting=good%20day", None, ""),
347 (StatusCode::OK, "ok"),
348 )
349 .await;
350
351 call(
352 svc,
353 ("GET", "/foo", None, ""),
354 (StatusCode::NOT_IMPLEMENTED, ""),
355 )
356 .await;
357 }
358
359 pub async fn match_post<S>(server: &mut ServerGuard, svc: &mut S)
360 where
361 S: Service<
362 Request<String>,
363 Response = Result<Response<Incoming>, ProxyError>,
364 Error = Infallible,
365 Future = RevProxyFuture,
366 >,
367 {
368 let _mk = server
369 .mock("POST", "/goo")
370 .match_body("test")
371 .with_body("ok")
372 .create_async()
373 .await;
374
375 call(svc, ("POST", "/foo", None, "test"), (StatusCode::OK, "ok")).await;
376
377 call(
378 svc,
379 ("PUT", "/foo", None, "test"),
380 (StatusCode::NOT_IMPLEMENTED, ""),
381 )
382 .await;
383
384 call(
385 svc,
386 ("POST", "/foo", None, "tests"),
387 (StatusCode::NOT_IMPLEMENTED, ""),
388 )
389 .await;
390 }
391
392 pub async fn match_header<S>(server: &mut ServerGuard, svc: &mut S)
393 where
394 S: Service<
395 Request<String>,
396 Response = Result<Response<Incoming>, ProxyError>,
397 Error = Infallible,
398 Future = RevProxyFuture,
399 >,
400 {
401 let _mk = server
402 .mock("POST", "/goo")
403 .match_header("content-type", "application/json")
404 .match_body(r#"{"key":"value"}"#)
405 .with_body("ok")
406 .create_async()
407 .await;
408
409 call(
410 svc,
411 (
412 "POST",
413 "/foo",
414 Some("application/json"),
415 r#"{"key":"value"}"#,
416 ),
417 (StatusCode::OK, "ok"),
418 )
419 .await;
420
421 call(
422 svc,
423 ("POST", "/foo", None, r#"{"key":"value"}"#),
424 (StatusCode::NOT_IMPLEMENTED, ""),
425 )
426 .await;
427
428 call(
429 svc,
430 (
431 "POST",
432 "/foo",
433 Some("application/json"),
434 r#"{"key":"values"}"#,
435 ),
436 (StatusCode::NOT_IMPLEMENTED, ""),
437 )
438 .await;
439 }
440}