1use rama_core::error::BoxErrorExt as _;
2use std::convert::TryFrom;
3use std::sync::Arc;
4use std::time::Duration;
5
6use rama_core::{
7 Layer, Service,
8 error::{BoxError, ErrorContext as _, ErrorExt as _},
9 extensions::ExtensionsRef,
10 graceful::ShutdownGuard,
11 io::{BridgeIo, GracefulIo, Io},
12 layer::{
13 ArcLayer, ConsumeErrLayer,
14 consume_err::{StaticOutput, Trace},
15 },
16 rt::Executor,
17 service::service_fn,
18 telemetry::tracing,
19};
20use rama_http::{
21 Body, HeaderName, HeaderValue, Method, Request, Response, StatusCode, Version,
22 conn::{H2ServerContextParams, TargetHttpVersion},
23 service::web::response::IntoResponse,
24};
25use rama_http_core::server::conn::{
26 auto::Builder as AutoConnBuilder, http1::Builder as Http1ConnBuilder,
27 http2::Builder as H2ConnBuilder,
28};
29use rama_http_types::proto::h2::frame::Settings;
30use rama_net::client::EstablishedClientConnection;
31use rama_net::uri::Uri;
32
33use tokio::sync::Mutex;
34use tokio_util::sync::CancellationToken;
35
36use crate::{
37 client::{HttpClientService, http_connect, http2_eager_handshake},
38 server::HttpServer,
39};
40
41pub const DEFAULT_EAGER_PEER_SETTINGS_TIMEOUT: Duration = Duration::from_secs(5);
47
48#[derive(Debug, Clone, Copy, Default)]
49pub struct DefaultErrorResponse;
52
53impl DefaultErrorResponse {
54 #[inline(always)]
55 pub fn new() -> Self {
56 Self
57 }
58
59 #[inline(always)]
60 fn response() -> Response {
61 (
62 [
63 (
64 HeaderName::from_static("x-proxy-framework-name"),
65 HeaderValue::from_static(rama_utils::info::NAME),
66 ),
67 (
68 HeaderName::from_static("x-proxy-framework-version"),
69 HeaderValue::from_static(rama_utils::info::VERSION),
70 ),
71 ],
72 StatusCode::BAD_GATEWAY,
73 )
74 .into_response()
75 }
76
77 #[inline(always)]
78 fn response_for_version(version: Version) -> Response {
79 let mut response = Self::response();
80 if matches!(
81 version,
82 Version::HTTP_09 | Version::HTTP_10 | Version::HTTP_11
83 ) {
84 response.headers_mut().insert(
85 HeaderName::from_static("connection"),
86 HeaderValue::from_static("close"),
87 );
88 }
89 response
90 }
91
92 #[inline(always)]
93 fn best_effort_response_after_ingress_cancellation(version: Version) -> Response {
94 Self::response_for_version(version)
98 }
99
100 #[inline(always)]
101 fn cancel_ingress_and_return_best_effort_response(
102 version: Version,
103 close_ingress: &CancellationToken,
104 ) -> Response {
105 close_ingress.cancel();
106 Self::best_effort_response_after_ingress_cancellation(version)
107 }
108}
109
110impl From<DefaultErrorResponse> for Response {
111 #[inline(always)]
112 fn from(_: DefaultErrorResponse) -> Self {
113 DefaultErrorResponse::response()
114 }
115}
116
117pub type DefaultMiddleware = (
121 ConsumeErrLayer<Trace, StaticOutput<DefaultErrorResponse>>,
122 ArcLayer,
123);
124
125#[derive(Debug, Clone)]
126pub struct HttpMitmRelay<M = DefaultMiddleware> {
134 http_server: HttpServer<AutoConnBuilder>,
135 middleware: M,
136 exec: Executor,
137 eager_peer_settings_timeout: Duration,
138}
139
140impl HttpMitmRelay {
141 #[inline(always)]
142 #[must_use]
143 pub fn new(exec: Executor) -> Self {
145 let mut http_server = HttpServer::auto(exec.clone());
150 http_server.h2_mut().set_enable_connect_protocol();
151 Self {
152 http_server,
153 middleware: (
154 ConsumeErrLayer::trace_as_debug().with_response(DefaultErrorResponse),
155 ArcLayer::new(),
156 ),
157 exec,
158 eager_peer_settings_timeout: DEFAULT_EAGER_PEER_SETTINGS_TIMEOUT,
159 }
160 }
161
162 pub fn with_http_middleware<M>(self, middleware: M) -> HttpMitmRelay<M> {
167 HttpMitmRelay {
168 http_server: self.http_server,
169 middleware,
170 exec: self.exec,
171 eager_peer_settings_timeout: self.eager_peer_settings_timeout,
172 }
173 }
174}
175
176impl<M> HttpMitmRelay<M> {
177 #[inline(always)]
178 pub fn http1(&self) -> &Http1ConnBuilder {
180 self.http_server.http1()
181 }
182
183 #[inline(always)]
184 pub fn http1_mut(&mut self) -> &mut Http1ConnBuilder {
186 self.http_server.http1_mut()
187 }
188
189 #[inline(always)]
190 pub fn h2(&self) -> &H2ConnBuilder {
192 self.http_server.h2()
193 }
194
195 #[inline(always)]
196 pub fn h2_mut(&mut self) -> &mut H2ConnBuilder {
198 self.http_server.h2_mut()
199 }
200
201 rama_utils::macros::generate_set_and_with! {
202 pub fn eager_peer_settings_timeout(mut self, timeout: Duration) -> Self {
208 self.eager_peer_settings_timeout = timeout;
209 self
210 }
211 }
212}
213
214impl<Ingress, Egress, M> Service<BridgeIo<Ingress, Egress>> for HttpMitmRelay<M>
215where
216 Ingress: Io + Unpin + ExtensionsRef,
217 Egress: Io + Unpin + ExtensionsRef,
218 M: Layer<
219 HttpClientService<Body>,
220 Service: Service<Request, Output = Response, Error: Into<BoxError>> + Clone,
221 >
222 + Send
223 + Sync
224 + 'static
225 + Clone,
226{
227 type Output = ();
228 type Error = BoxError;
229
230 async fn serve(
231 &self,
232 BridgeIo(ingress_stream, egress_stream): BridgeIo<Ingress, Egress>,
233 ) -> Result<Self::Output, Self::Error> {
234 let token = CancellationToken::new();
235 let request_guard = self.exec.guard().cloned();
236
237 tracing::debug!("HTTP MITM Relay: start");
238
239 let egress_is_h2 = egress_stream
246 .extensions()
247 .get_ref::<TargetHttpVersion>()
248 .map(|t| t.0 == Version::HTTP_2)
249 .unwrap_or(false);
250
251 let relay_state = if egress_is_h2 {
252 let exec = request_guard
253 .clone()
254 .map_or_else(Executor::default, Executor::graceful);
255 match http2_eager_handshake::<_, Body>(egress_stream, exec).await {
256 Ok((conn, peer_handle)) => {
257 let timeout_dur = self.eager_peer_settings_timeout;
258 let peer_settings =
259 tokio::time::timeout(timeout_dur, peer_handle.await_settings())
260 .await
261 .unwrap_or_else(|_| {
262 tracing::debug!(
263 "eager egress h2 peer SETTINGS not received within {:?}",
264 timeout_dur,
265 );
266 None
267 });
268 let mirrored = if let Some(peer) = peer_settings.as_ref() {
269 tracing::trace!("mirroring upstream h2 SETTINGS onto ingress: {peer:?}",);
270 mirror_peer_settings(&peer.0)
273 } else {
274 tracing::debug!(
278 "no upstream h2 SETTINGS captured; forcing CONNECT off on ingress",
279 );
280 H2ServerContextParams {
281 enable_connect_protocol: Some(false),
282 ..H2ServerContextParams::default()
283 }
284 };
285 ingress_stream.extensions().insert(mirrored);
286 let client = self.middleware.clone().layer(conn);
287 Arc::new(Mutex::new(RelayState::Http2 { client }))
288 }
289 Err(err) => {
290 tracing::debug!("eager egress h2 handshake failed: {err}");
291 return Err(err.into());
292 }
293 }
294 } else {
295 Arc::new(Mutex::new(RelayState::new(
296 egress_stream,
297 self.middleware.clone(),
298 )))
299 };
300
301 let result = self
302 .http_server
303 .serve(
304 GracefulIo::new(token.clone().cancelled_owned(), ingress_stream),
305 service_fn(move |req: Request| {
306 let relay_state = relay_state.clone();
307 let close_ingress = token.clone();
308 let guard = request_guard.clone();
309 async move {
310 Ok(handle_relay_request(&relay_state, req, guard, close_ingress).await)
311 }
312 }),
313 )
314 .await
315 .context("serve HTTP MITM relay");
316
317 tracing::debug!("HTTP MITM Relay: Shutdown: done");
318 result
319 }
320}
321
322#[derive(Debug, Clone, Copy)]
323enum RelayMode {
324 Http1,
325 Http2,
326}
327
328impl RelayMode {
329 fn as_str(self) -> &'static str {
330 match self {
331 Self::Http1 => "http1",
332 Self::Http2 => "http2",
333 }
334 }
335}
336
337impl TryFrom<Version> for RelayMode {
338 type Error = BoxError;
339
340 fn try_from(version: Version) -> Result<Self, Self::Error> {
341 match version {
342 Version::HTTP_2 => Ok(Self::Http2),
343 Version::HTTP_09 | Version::HTTP_10 | Version::HTTP_11 => Ok(Self::Http1),
344 version => Err(
345 BoxError::from_static_str("unsupported request version for MITM relay")
346 .context_debug_field("version", version),
347 ),
348 }
349 }
350}
351
352enum RelayState<Egress, Middleware>
353where
354 Egress: Io + Unpin + ExtensionsRef,
355 Middleware: Layer<HttpClientService<Body>>,
356{
357 Uninitialized {
358 egress_stream: Option<Egress>,
359 middleware: Middleware,
360 },
361 Http1 {
362 client: Middleware::Service,
363 },
364 Http2 {
365 client: Middleware::Service,
366 },
367 Closed,
368}
369
370impl<Egress, Middleware> RelayState<Egress, Middleware>
371where
372 Egress: Io + Unpin + ExtensionsRef,
373 Middleware: Layer<HttpClientService<Body>>,
374{
375 fn new(egress_stream: Egress, middleware: Middleware) -> Self {
376 Self::Uninitialized {
377 egress_stream: Some(egress_stream),
378 middleware,
379 }
380 }
381}
382
383async fn handle_relay_request<Egress, Middleware>(
384 relay_state: &Arc<Mutex<RelayState<Egress, Middleware>>>,
385 req: Request,
386 guard: Option<ShutdownGuard>,
387 close_ingress: CancellationToken,
388) -> Response
389where
390 Egress: Io + Unpin + ExtensionsRef,
391 Middleware: Layer<
392 HttpClientService<Body>,
393 Service: Service<Request, Output = Response, Error: Into<BoxError>> + Clone,
394 > + Clone,
395{
396 let method = req.method().clone();
397 let uri = req.uri().clone();
398 let version = req.version();
399
400 let relay_mode = match RelayMode::try_from(version) {
401 Ok(mode) => mode,
402 Err(err) => {
403 tracing::debug!("failed to derive relay mode from request version: {err}");
404 return DefaultErrorResponse::cancel_ingress_and_return_best_effort_response(
405 version,
406 &close_ingress,
407 );
408 }
409 };
410 tracing::trace!(
411 http.request.method = %method,
412 url.full = %uri,
413 ?version,
414 mode = relay_mode.as_str(),
415 "dispatching request on MITM relay egress"
416 );
417
418 match relay_mode {
419 RelayMode::Http1 => {
420 let mut state = relay_state.lock().await;
421 let resp = serve_http1_request(
422 &mut *state,
423 req,
424 guard,
425 &method,
426 &uri,
427 version,
428 close_ingress.clone(),
429 )
430 .await;
431 if let Ok(ref resp) = resp {
432 tracing::trace!(
433 http.request.method = %method,
434 url.full = %uri,
435 ?version,
436 http.response.status_code = resp.status().as_u16(),
437 "received response from MITM relay egress"
438 );
439 }
440 resp.unwrap_or_else(|_| {
441 DefaultErrorResponse::best_effort_response_after_ingress_cancellation(version)
442 })
443 }
444 RelayMode::Http2 => {
445 let client_and_req = {
450 let mut state = relay_state.lock().await;
451 relay_connect_http2_if_needed(&mut *state, req, guard, close_ingress.clone()).await
452 };
453
454 match client_and_req {
455 Ok((client, req)) => {
456 let resp = serve_relay_request(
457 &client,
458 req,
459 &method,
460 &uri,
461 version,
462 close_ingress.clone(),
463 relay_state,
464 )
465 .await;
466 if let Ok(ref resp) = resp {
467 tracing::trace!(
468 http.request.method = %method,
469 url.full = %uri,
470 ?version,
471 http.response.status_code = resp.status().as_u16(),
472 "received response from MITM relay egress"
473 );
474 }
475 resp.unwrap_or_else(|_| {
476 DefaultErrorResponse::best_effort_response_after_ingress_cancellation(
477 version,
478 )
479 })
480 }
481 Err(resp) => resp,
482 }
483 }
484 }
485}
486
487async fn serve_http1_request<Egress, Middleware>(
488 state: &mut RelayState<Egress, Middleware>,
489 req: Request,
490 guard: Option<ShutdownGuard>,
491 method: &Method,
492 uri: &Uri,
493 version: Version,
494 close_ingress: CancellationToken,
495) -> Result<Response, BoxError>
496where
497 Egress: Io + Unpin + ExtensionsRef,
498 Middleware: Layer<
499 HttpClientService<Body>,
500 Service: Service<Request, Output = Response, Error: Into<BoxError>> + Clone,
501 > + Clone,
502{
503 let req = match relay_connect_http1_if_needed(state, req, guard, close_ingress.clone()).await {
504 Ok(req) => req,
505 Err(resp) => return Ok(resp),
506 };
507
508 match state {
509 RelayState::Http1 { client } => {
510 let result = client.serve(req).await.into_box_error();
511 match result {
512 Ok(resp) => Ok(resp),
513 Err(err) => {
514 tracing::debug!(
515 http.request.method = %method,
516 url.full = %uri,
517 ?version,
518 "upstream MITM relay request failed: {err}"
519 );
520 *state = RelayState::Closed;
521 close_ingress.cancel();
522 Err(err)
523 }
524 }
525 }
526 RelayState::Closed => Ok(DefaultErrorResponse::response_for_version(version)),
527 RelayState::Http2 { .. } | RelayState::Uninitialized { .. } => {
528 close_ingress.cancel();
529 *state = RelayState::Closed;
530 Ok(DefaultErrorResponse::best_effort_response_after_ingress_cancellation(version))
531 }
532 }
533}
534
535async fn relay_connect_http1_if_needed<Egress, Middleware>(
536 state: &mut RelayState<Egress, Middleware>,
537 req: Request,
538 guard: Option<ShutdownGuard>,
539 close_ingress: CancellationToken,
540) -> Result<Request, Response>
541where
542 Egress: Io + Unpin + ExtensionsRef,
543 Middleware: Layer<
544 HttpClientService<Body>,
545 Service: Service<Request, Output = Response, Error: Into<BoxError>> + Clone,
546 > + Clone,
547{
548 match state {
549 RelayState::Http1 { .. } => Ok(req),
550 RelayState::Http2 { .. } => {
551 tracing::debug!("received HTTP/1 relay request on HTTP/2 relay state; closing relay");
552 *state = RelayState::Closed;
553 Err(
554 DefaultErrorResponse::cancel_ingress_and_return_best_effort_response(
555 req.version(),
556 &close_ingress,
557 ),
558 )
559 }
560 RelayState::Closed => Err(
561 DefaultErrorResponse::cancel_ingress_and_return_best_effort_response(
562 req.version(),
563 &close_ingress,
564 ),
565 ),
566 RelayState::Uninitialized { .. } => {
567 let req = connect_relay(state, req, guard, close_ingress.clone()).await?;
568 if let RelayState::Http1 { .. } = state {
569 Ok(req)
570 } else {
571 tracing::debug!("failed to initialize HTTP/1 relay state from first request");
572 *state = RelayState::Closed;
573 Err(
574 DefaultErrorResponse::cancel_ingress_and_return_best_effort_response(
575 req.version(),
576 &close_ingress,
577 ),
578 )
579 }
580 }
581 }
582}
583
584async fn relay_connect_http2_if_needed<Egress, Middleware>(
585 state: &mut RelayState<Egress, Middleware>,
586 req: Request,
587 guard: Option<ShutdownGuard>,
588 close_ingress: CancellationToken,
589) -> Result<(Middleware::Service, Request), Response>
590where
591 Egress: Io + Unpin + ExtensionsRef,
592 Middleware: Layer<
593 HttpClientService<Body>,
594 Service: Service<Request, Output = Response, Error: Into<BoxError>> + Clone,
595 > + Clone,
596{
597 match state {
598 RelayState::Http2 { client } => Ok((client.clone(), req)),
599 RelayState::Http1 { .. } => {
600 tracing::debug!("received HTTP/2 relay request on HTTP/1 relay state; closing relay");
601 *state = RelayState::Closed;
602 Err(
603 DefaultErrorResponse::cancel_ingress_and_return_best_effort_response(
604 req.version(),
605 &close_ingress,
606 ),
607 )
608 }
609 RelayState::Closed => Err(
610 DefaultErrorResponse::cancel_ingress_and_return_best_effort_response(
611 req.version(),
612 &close_ingress,
613 ),
614 ),
615 RelayState::Uninitialized { .. } => {
616 let version = req.version();
617 let req = connect_relay(state, req, guard, close_ingress.clone()).await?;
618 if let RelayState::Http2 { client } = state {
619 Ok((client.clone(), req))
620 } else {
621 tracing::debug!("failed to initialize HTTP/2 relay state from first request");
622 *state = RelayState::Closed;
623 Err(
624 DefaultErrorResponse::cancel_ingress_and_return_best_effort_response(
625 version,
626 &close_ingress,
627 ),
628 )
629 }
630 }
631 }
632}
633
634async fn connect_relay<Egress, Middleware>(
635 state: &mut RelayState<Egress, Middleware>,
636 req: Request,
637 guard: Option<ShutdownGuard>,
638 close_ingress: CancellationToken,
639) -> Result<Request, Response>
640where
641 Egress: Io + Unpin + ExtensionsRef,
642 Middleware: Layer<
643 HttpClientService<Body>,
644 Service: Service<Request, Output = Response, Error: Into<BoxError>> + Clone,
645 > + Clone,
646{
647 let RelayState::Uninitialized {
648 egress_stream,
649 middleware,
650 } = state
651 else {
652 return Ok(req);
653 };
654
655 let req_version = req.version();
656 let Some(egress_stream) = egress_stream.take() else {
657 *state = RelayState::Closed;
658 return Err(
659 DefaultErrorResponse::cancel_ingress_and_return_best_effort_response(
660 req_version,
661 &close_ingress,
662 ),
663 );
664 };
665
666 let exec = guard.map_or_else(Executor::default, Executor::graceful);
667 match http_connect(egress_stream, req, exec).await {
668 Ok(EstablishedClientConnection { input, conn }) => {
669 let version = input.version();
670 let client = middleware.layer(conn);
671 match RelayMode::try_from(version) {
672 Ok(RelayMode::Http1) => {
673 *state = RelayState::Http1 { client };
674 Ok(input)
675 }
676 Ok(RelayMode::Http2) => {
677 *state = RelayState::Http2 { client };
678 Ok(input)
679 }
680 Err(err) => {
681 tracing::debug!("failed to derive relay mode after egress connect: {err}");
682 *state = RelayState::Closed;
683 Err(
684 DefaultErrorResponse::cancel_ingress_and_return_best_effort_response(
685 version,
686 &close_ingress,
687 ),
688 )
689 }
690 }
691 }
692 Err(err) => {
693 tracing::debug!("failed to establish egress HTTP connection: {err}");
694 *state = RelayState::Closed;
695 Err(
696 DefaultErrorResponse::cancel_ingress_and_return_best_effort_response(
697 req_version,
698 &close_ingress,
699 ),
700 )
701 }
702 }
703}
704
705async fn serve_relay_request<Egress, Middleware, Client>(
706 client: &Client,
707 req: Request,
708 method: &Method,
709 uri: &Uri,
710 version: Version,
711 close_ingress: CancellationToken,
712 relay_state: &Arc<Mutex<RelayState<Egress, Middleware>>>,
713) -> Result<Response, BoxError>
714where
715 Egress: Io + Unpin + ExtensionsRef,
716 Middleware: Layer<HttpClientService<Body>>,
717 Client: Service<Request, Output = Response, Error: Into<BoxError>>,
718{
719 match client.serve(req).await {
726 Ok(resp) => Ok(resp),
727 Err(err) => {
728 let err = err.into_box_error();
729 tracing::debug!(
730 http.request.method = %method,
731 url.full = %uri,
732 ?version,
733 "upstream MITM relay request failed: {err}"
734 );
735 if egress_error_is_stream_scoped(&err) {
743 return Ok(DefaultErrorResponse::response_for_version(version));
744 }
745 *relay_state.lock().await = RelayState::Closed;
746 close_ingress.cancel();
747 Err(err)
748 }
749 }
750}
751
752fn mirror_peer_settings(settings: &Settings) -> H2ServerContextParams {
764 let cfg = &settings.config;
765 H2ServerContextParams {
766 enable_connect_protocol: Some(cfg.enable_connect_protocol.map(|v| v != 0).unwrap_or(false)),
767 max_concurrent_streams: cfg.max_concurrent_streams,
768 header_table_size: None,
770 max_frame_size: None,
771 max_header_list_size: None,
772 initial_stream_window_size: None,
773 initial_connection_window_size: None,
774 adaptive_window: None,
775 }
776}
777
778fn egress_error_is_stream_scoped(err: &BoxError) -> bool {
782 if let Some(h2) = err.downcast_ref::<rama_http_core::h2::Error>() {
783 return h2.is_reset();
784 }
785 let mut current = err.source();
786 while let Some(cause) = current {
787 if let Some(h2) = cause.downcast_ref::<rama_http_core::h2::Error>() {
788 return h2.is_reset();
789 }
790 current = cause.source();
791 }
792 false
793}