#![expect(
clippy::expect_used,
reason = "example/test/bench: panic-on-error and print-for-output are the standard patterns for demos and harnesses"
)]
use rama::{
Layer, Service,
error::{BoxError, ErrorContext},
extensions::ExtensionsRef,
http::{
BodyLimitLayer, HeaderName, HeaderValue,
client::EasyHttpWebClient,
layer::{
map_response_body::MapResponseBodyLayer,
proxy_auth::ProxyAuthLayer,
set_header::{SetRequestHeaderLayer, SetResponseHeaderLayer},
trace::TraceLayer,
upgrade::{DefaultHttpProxyConnectReplyService, UpgradeLayer},
},
matcher::MethodMatcher,
proxy::mitm::{DefaultErrorResponse, HttpMitmRelay},
server::HttpServer,
},
io::Io,
layer::{ArcLayer, ConsumeErrLayer},
net::{http::server::HttpPeekRouter, proxy::IoForwardService, user::credentials::basic},
rt::Executor,
tcp::{proxy::IoToProxyBridgeIoLayer, server::TcpListener},
telemetry::tracing::{
self,
level_filters::LevelFilter,
subscriber::{EnvFilter, fmt, layer::SubscriberExt, util::SubscriberInitExt},
},
tls::boring::proxy::TlsMitmRelay,
tls::server::{PeekTlsClientHelloService, SelfSignedData},
utils::octets::mib,
};
use std::{convert::Infallible, sync::Arc, time::Duration};
#[tokio::main]
async fn main() -> Result<(), BoxError> {
tracing::subscriber::registry()
.with(fmt::layer())
.with(
EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.from_env_lossy(),
)
.init();
let graceful = rama::graceful::Shutdown::default();
let exec = Executor::graceful(graceful.guard());
let mitm_svc = new_mitm_svc(&exec).context("build MITM service")?;
graceful.spawn_task_fn(async move |guard| {
let tcp_service = TcpListener::build(Executor::graceful(guard.clone()))
.bind_address("127.0.0.1:62049")
.await
.expect("bind tcp proxy to 127.0.0.1:62049");
let http_service = HttpServer::auto(exec).service(Arc::new(
(
TraceLayer::new_for_http(),
ConsumeErrLayer::default(),
ProxyAuthLayer::new(basic!("john", "secret")),
UpgradeLayer::new(
Executor::graceful(guard.clone()),
MethodMatcher::CONNECT,
DefaultHttpProxyConnectReplyService::new(),
mitm_svc,
),
(
SetRequestHeaderLayer::overriding(
HeaderName::from_static("x-observed"),
HeaderValue::from_static("1"),
),
SetResponseHeaderLayer::overriding(
HeaderName::from_static("x-proxy"),
HeaderValue::from_static(rama::utils::info::NAME),
),
SetResponseHeaderLayer::overriding(
HeaderName::from_static("x-proxy-version"),
HeaderValue::from_static(rama::utils::info::VERSION),
),
),
)
.into_layer(EasyHttpWebClient::default_with_executor(
Executor::graceful(guard),
)),
));
tcp_service
.serve(BodyLimitLayer::symmetric(mib(2)).into_layer(http_service))
.await;
});
graceful
.shutdown_with_limit(Duration::from_secs(30))
.await
.context("graceful shutdown")?;
Ok(())
}
fn new_mitm_svc<Ingress: Io + Unpin + ExtensionsRef>(
exec: &Executor,
) -> Result<impl Service<Ingress, Output = (), Error = Infallible> + Clone, BoxError> {
let http_mitm_relay = HttpMitmRelay::new(exec.clone()).with_http_middleware((
ConsumeErrLayer::trace_as_debug().with_response(DefaultErrorResponse::new()),
MapResponseBodyLayer::new_boxed_streaming_body(),
TraceLayer::new_for_http(),
SetRequestHeaderLayer::overriding(
HeaderName::from_static("x-observed"),
HeaderValue::from_static("1"),
),
SetResponseHeaderLayer::overriding(
HeaderName::from_static("x-proxy"),
HeaderValue::from_static(rama::utils::info::NAME),
),
SetResponseHeaderLayer::overriding(
HeaderName::from_static("x-proxy-version"),
HeaderValue::from_static(rama::utils::info::VERSION),
),
ArcLayer::new(),
));
let maybe_http_relay =
HttpPeekRouter::new(http_mitm_relay).with_fallback(IoForwardService::new(exec.clone()));
let tls_mitm_relay = TlsMitmRelay::try_new_with_cached_self_signed_issuer(&SelfSignedData {
organisation_name: Some("HTTP MITM Relay Proxy Boring Example".to_owned()),
..Default::default()
})
.context("build TLS mitm relay")?;
let app_mitm_layer =
PeekTlsClientHelloService::new(tls_mitm_relay.into_layer(maybe_http_relay.clone()))
.with_fallback(maybe_http_relay);
Ok(Arc::new(
(
ConsumeErrLayer::trace_as_debug(),
IoToProxyBridgeIoLayer::extension_connector_target().with_connector(
rama::dns::client::DnsConnector::new(
rama::tcp::client::service::TcpConnector::new(),
),
),
)
.into_layer(app_mitm_layer),
))
}