Skip to main content

tower_proxy/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! `tower-proxy` is tower [`Service`s](tower_service::Service) that performs "reverse
4//! proxy" with various rewriting rules.
5//!
6//! Internally these services use [`hyper_util::client::legacy::Client`] to send an incoming request to another
7//! server. The [`connector`](hyper_util::client::legacy::connect::Connect) for a client can be
8#![cfg_attr(
9    not(any(feature = "https", feature = "nativetls", feature = "__rustls")),
10    doc = "[`HttpConnector`](hyper_util::client::legacy::connect::HttpConnector),"
11)]
12#![cfg_attr(
13    all(
14        any(feature = "https", feature = "nativetls"),
15        not(feature = "__rustls")
16    ),
17    doc = "[`HttpConnector`](hyper_util::client::legacy::connect::HttpConnector), [`HttpsConnector`](hyper_tls::HttpsConnector),"
18)]
19#![cfg_attr(
20    all(
21        not(any(feature = "https", feature = "nativetls")),
22        feature = "__rustls"
23    ),
24    doc = "[`HttpConnector`](hyper_util::client::legacy::connect::HttpConnector), [`client::RustlsConnector`],"
25)]
26#![cfg_attr(
27    all(any(feature = "https", feature = "nativetls"), feature = "__rustls"),
28    doc = "[`HttpConnector`](hyper_util::client::legacy::connect::HttpConnector), [`HttpsConnector`](hyper_tls::HttpsConnector), [`client::RustlsConnector`],"
29)]
30//! or any ones whichever you want.
31//!
32//! # Examples
33//!
34//! There are two types of services, [`OneshotService`] and [`ReusedService`]. The
35//! [`OneshotService`] *owns* the `Client`, while the [`ReusedService`] *shares* the `Client`
36//! via [`Arc`](std::sync::Arc).
37//!
38//!
39//! ## General usage
40//!
41//! ```
42//! # async fn run_test() {
43//! use tower_proxy::ReusedServiceBuilder;
44//! use tower_proxy::{ReplaceAll, ReplaceN};
45//!
46//! use hyper::body::Bytes;
47//! use http_body_util::Full;
48//! use http::Request;
49//! use tower_service::Service as _;
50//!
51//! let svc_builder = tower_proxy::builder_http("example.com:1234").unwrap();
52//!
53//! let req1 = Request::builder()
54//!     .method("GET")
55//!     .uri("https://myserver.com/foo/bar/foo")
56//!     .body(Full::new(Bytes::new()))
57//!     .unwrap();
58//!
59//! // Clones Arc<Client>
60//! let mut svc1 = svc_builder.build(ReplaceAll("foo", "baz"));
61//! // http://example.com:1234/baz/bar/baz
62//! let _res = svc1.call(req1).await.unwrap();
63//!
64//! let req2 = Request::builder()
65//!     .method("POST")
66//!     .uri("https://myserver.com/foo/bar/foo")
67//!     .header("Content-Type", "application/x-www-form-urlencoded")
68//!     .body(Full::new(Bytes::from("key=value")))
69//!     .unwrap();
70//!
71//! let mut svc2 = svc_builder.build(ReplaceN("foo", "baz", 1));
72//! // http://example.com:1234/baz/bar/foo
73//! let _res = svc2.call(req2).await.unwrap();
74//! # }
75//! ```
76//!
77//! In this example, the `svc1` and `svc2` shares the same `Client`, holding the `Arc<Client>`s
78//! inside them.
79//!
80//! For more information of rewriting rules (`ReplaceAll`, `ReplaceN` *etc.*), see the
81//! documentations of [`rewrite`].
82//!
83//!
84//! ## With axum
85//!
86//! ```
87//! # #[cfg(feature = "axum")] {
88//! use tower_proxy::ReusedServiceBuilder;
89//! use tower_proxy::{TrimPrefix, AppendSuffix, Static};
90//!
91//! use axum::Router;
92//!
93//! #[tokio::main]
94//! async fn main() {
95//!     let host1 = tower_proxy::builder_http("example.com").unwrap();
96//!     let host2 = tower_proxy::builder_http("example.net:1234").unwrap();
97//!
98//!     let app = Router::new()
99//!         .route_service("/healthcheck", host1.build(Static("/")))
100//!         .route_service("/users/{*path}", host1.build(TrimPrefix("/users")))
101//!         .route_service("/posts", host2.build(AppendSuffix("/")));
102//!
103//!     let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
104//!        .await
105//!        .unwrap();
106//!
107//!    axum::serve(listener, app).await.unwrap();
108//! }
109//! # }
110//! ```
111//!
112//!
113//! # Return Types
114//!
115//! The return type ([`std::future::Future::Output`]) of [`ReusedService`] and
116//! [`OneshotService`] is `Result<Result<Response<Incoming>, ProxyError>, Infallible>`.
117#![cfg_attr(
118    feature = "axum",
119    doc = "This is because axum's [`Router`](axum::Router) accepts only such `Service`s."
120)]
121//!
122#![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//!
132//! # Features
133//!
134//! By default only `http1` is enabled.
135//!
136//! - `http1`: uses `hyper/http1`
137//! - `http2`: uses `hyper/http2`
138//! - `https`: alias to `nativetls`
139//! - `nativetls`: uses the `hyper-tls` crate
140//! - `rustls`: alias to `rustls-webpki-roots`
141//! - `rustls-webpki-roots`: uses the `hyper-rustls` crate, with the feature `webpki-roots`
142//! - `rustls-native-roots`: uses the `hyper-rustls` crate, with the feature `rustls-native-certs`
143//! - `rustls-http2`: `http2` plus `rustls`, and `rustls/http2` is enabled
144
145#![cfg_attr(
146    feature = "axum",
147    doc = " - `axum`: implements [`IntoResponse`](axum::response::IntoResponse) for [`ProxyError`]"
148)]
149//! You must turn on either `http1`or `http2`. You cannot use the services if, for example, only
150//! the `https` feature is on.
151//!
152//! Through this document, we use `rustls` to mean *any* of `rustls*` features unless otherwise
153//! specified.
154
155mod 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};
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://test.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 match_query<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            .match_query(Matcher::UrlEncoded("greeting".into(), "good day".into()))
311            .with_body("ok")
312            .create_async()
313            .await;
314
315        call(
316            svc,
317            ("GET", "/foo?greeting=good%20day", None, ""),
318            (StatusCode::OK, "ok"),
319        )
320        .await;
321
322        call(
323            svc,
324            ("GET", "/foo", None, ""),
325            (StatusCode::NOT_IMPLEMENTED, ""),
326        )
327        .await;
328    }
329
330    pub async fn match_post<S>(server: &mut ServerGuard, svc: &mut S)
331    where
332        S: Service<
333                Request<String>,
334                Response = Result<Response<Incoming>, ProxyError>,
335                Error = Infallible,
336                Future = RevProxyFuture,
337            >,
338    {
339        let _mk = server
340            .mock("POST", "/goo")
341            .match_body("test")
342            .with_body("ok")
343            .create_async()
344            .await;
345
346        call(svc, ("POST", "/foo", None, "test"), (StatusCode::OK, "ok")).await;
347
348        call(
349            svc,
350            ("PUT", "/foo", None, "test"),
351            (StatusCode::NOT_IMPLEMENTED, ""),
352        )
353        .await;
354
355        call(
356            svc,
357            ("POST", "/foo", None, "tests"),
358            (StatusCode::NOT_IMPLEMENTED, ""),
359        )
360        .await;
361    }
362
363    pub async fn match_header<S>(server: &mut ServerGuard, svc: &mut S)
364    where
365        S: Service<
366                Request<String>,
367                Response = Result<Response<Incoming>, ProxyError>,
368                Error = Infallible,
369                Future = RevProxyFuture,
370            >,
371    {
372        let _mk = server
373            .mock("POST", "/goo")
374            .match_header("content-type", "application/json")
375            .match_body(r#"{"key":"value"}"#)
376            .with_body("ok")
377            .create_async()
378            .await;
379
380        call(
381            svc,
382            (
383                "POST",
384                "/foo",
385                Some("application/json"),
386                r#"{"key":"value"}"#,
387            ),
388            (StatusCode::OK, "ok"),
389        )
390        .await;
391
392        call(
393            svc,
394            ("POST", "/foo", None, r#"{"key":"value"}"#),
395            (StatusCode::NOT_IMPLEMENTED, ""),
396        )
397        .await;
398
399        call(
400            svc,
401            (
402                "POST",
403                "/foo",
404                Some("application/json"),
405                r#"{"key":"values"}"#,
406            ),
407            (StatusCode::NOT_IMPLEMENTED, ""),
408        )
409        .await;
410    }
411}