Skip to content

Commit f8d0127

Browse files
drcaramelsyrupeaufavor
authored andcommitted
Allow reusing session on errors prior to proxy upstream
Allow session reuse if configured in `fail_to_proxy`. Applies only to errors prior to proxying upstream, for now. Previously any short-circuiting error during request processing would result in closing the connection to downstream. This is unnecessary if there was an internal error that simply resulted in an error response being written downstream. Note that any `set_keepalive` on the Session itself is still respected, and this API currently does not overwrite reuse decisions made while proxying upstream. The user is responsible for implications of reusing the session in this manner, such as draining any remaining request body.
1 parent c3ed76d commit f8d0127

File tree

3 files changed

+70
-46
lines changed

3 files changed

+70
-46
lines changed

.bleep

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
98d239a60a476873e1de444763a555802aa41f6c
1+
e6597ea67a5a6c4fcdd79d63de1e3d04a46d7a35

pingora-proxy/src/lib.rs

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ use subrequest::Ctx as SubReqCtx;
7979

8080
pub use proxy_cache::range_filter::{range_header_filter, RangeType};
8181
pub use proxy_purge::PurgeStatus;
82-
pub use proxy_trait::ProxyHttp;
82+
pub use proxy_trait::{FailToProxy, ProxyHttp};
8383

8484
pub mod prelude {
8585
pub use crate::{http_proxy_service, ProxyHttp, Session};
@@ -493,10 +493,9 @@ impl<SV> HttpProxy<SV> {
493493
.early_request_filter(&mut session, &mut ctx)
494494
.await
495495
{
496-
self.handle_error(&mut session, &mut ctx, e, "Fail to early filter request:")
496+
return self
497+
.handle_error(session, &mut ctx, e, "Fail to early filter request:")
497498
.await;
498-
self.cleanup_sub_req(&mut session);
499-
return None;
500499
}
501500

502501
let req = session.downstream_session.req_header_mut();
@@ -507,15 +506,14 @@ impl<SV> HttpProxy<SV> {
507506
.request_header_filter(req)
508507
.await
509508
{
510-
self.handle_error(
511-
&mut session,
512-
&mut ctx,
513-
e,
514-
"Failed in downstream modules request filter:",
515-
)
516-
.await;
517-
self.cleanup_sub_req(&mut session);
518-
return None;
509+
return self
510+
.handle_error(
511+
session,
512+
&mut ctx,
513+
e,
514+
"Failed in downstream modules request filter:",
515+
)
516+
.await;
519517
}
520518

521519
match self.inner.request_filter(&mut session, &mut ctx).await {
@@ -529,10 +527,9 @@ impl<SV> HttpProxy<SV> {
529527
/* else continue */
530528
}
531529
Err(e) => {
532-
self.handle_error(&mut session, &mut ctx, e, "Fail to filter request:")
530+
return self
531+
.handle_error(session, &mut ctx, e, "Fail to filter request:")
533532
.await;
534-
self.cleanup_sub_req(&mut session);
535-
return None;
536533
}
537534
}
538535

@@ -563,15 +560,14 @@ impl<SV> HttpProxy<SV> {
563560
match session.write_response_header_ref(&BAD_GATEWAY).await {
564561
Ok(()) => {}
565562
Err(e) => {
566-
self.handle_error(
567-
&mut session,
568-
&mut ctx,
569-
e,
570-
"Error responding with Bad Gateway:",
571-
)
572-
.await;
573-
574-
return None;
563+
return self
564+
.handle_error(
565+
session,
566+
&mut ctx,
567+
e,
568+
"Error responding with Bad Gateway:",
569+
)
570+
.await;
575571
}
576572
}
577573
}
@@ -585,14 +581,14 @@ impl<SV> HttpProxy<SV> {
585581
session.cache.disable(NoCacheReason::InternalError);
586582
}
587583

588-
self.handle_error(
589-
&mut session,
590-
&mut ctx,
591-
e,
592-
"Error deciding if we should proxy to upstream:",
593-
)
594-
.await;
595-
return None;
584+
return self
585+
.handle_error(
586+
session,
587+
&mut ctx,
588+
e,
589+
"Error deciding if we should proxy to upstream:",
590+
)
591+
.await;
596592
}
597593
}
598594

@@ -657,14 +653,14 @@ impl<SV> HttpProxy<SV> {
657653
};
658654
session.cache.disable(reason);
659655
}
660-
let status = self.inner.fail_to_proxy(&mut session, e, &mut ctx).await;
656+
let res = self.inner.fail_to_proxy(&mut session, e, &mut ctx).await;
661657

662658
// final error will have > 0 status unless downstream connection is dead
663659
if !self.inner.suppress_error_log(&session, &ctx, e) {
664660
error!(
665661
"Fail to proxy: {}, status: {}, tries: {}, retry: {}, {}",
666662
final_error.as_ref().unwrap(),
667-
status,
663+
res.error_code,
668664
retries,
669665
false, // we never retry here
670666
self.inner.request_summary(&session, &ctx)
@@ -679,23 +675,32 @@ impl<SV> HttpProxy<SV> {
679675

680676
async fn handle_error(
681677
&self,
682-
session: &mut Session,
678+
mut session: Session,
683679
ctx: &mut <SV as ProxyHttp>::CTX,
684680
e: Box<Error>,
685681
context: &str,
686-
) where
682+
) -> Option<Stream>
683+
where
687684
SV: ProxyHttp + Send + Sync + 'static,
688685
<SV as ProxyHttp>::CTX: Send + Sync,
689686
{
690-
if !self.inner.suppress_error_log(session, ctx, &e) {
687+
let res = self.inner.fail_to_proxy(&mut session, &e, ctx).await;
688+
if !self.inner.suppress_error_log(&session, ctx, &e) {
691689
error!(
692-
"{context} {}, {}",
690+
"{context} {}, status: {}, {}",
693691
e,
694-
self.inner.request_summary(session, ctx)
692+
res.error_code,
693+
self.inner.request_summary(&session, ctx)
695694
);
696695
}
697-
self.inner.fail_to_proxy(session, &e, ctx).await;
698-
self.inner.logging(session, Some(&e), ctx).await;
696+
self.inner.logging(&mut session, Some(&e), ctx).await;
697+
self.cleanup_sub_req(&mut session);
698+
699+
if res.can_reuse_downstream {
700+
session.downstream_session.finish().await.ok().flatten()
701+
} else {
702+
None
703+
}
699704
}
700705
}
701706

pingora-proxy/src/proxy_trait.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -391,8 +391,16 @@ pub trait ProxyHttp {
391391
///
392392
/// Users may write an error response to the downstream if the downstream is still writable.
393393
///
394-
/// The response status code of the error response maybe returned for logging purpose.
395-
async fn fail_to_proxy(&self, session: &mut Session, e: &Error, _ctx: &mut Self::CTX) -> u16
394+
/// The response status code of the error response may be returned for logging purposes.
395+
/// Additionally, the user can return whether this session may be reused in spite of the error.
396+
/// Today this reuse status is only respected for errors that occur prior to upstream peer
397+
/// selection, and the keepalive configured on the `Session` itself still takes precedent.
398+
async fn fail_to_proxy(
399+
&self,
400+
session: &mut Session,
401+
e: &Error,
402+
_ctx: &mut Self::CTX,
403+
) -> FailToProxy
396404
where
397405
Self::CTX: Send + Sync,
398406
{
@@ -419,7 +427,12 @@ pub trait ProxyHttp {
419427
error!("failed to send error response to downstream: {e}");
420428
});
421429
}
422-
code
430+
431+
FailToProxy {
432+
error_code: code,
433+
// default to no reuse, which is safest
434+
can_reuse_downstream: false,
435+
}
423436
}
424437

425438
/// Decide whether should serve stale when encountering an error or during revalidation
@@ -493,3 +506,9 @@ pub trait ProxyHttp {
493506
Ok(())
494507
}
495508
}
509+
510+
/// Context struct returned by `fail_to_proxy`.
511+
pub struct FailToProxy {
512+
pub error_code: u16,
513+
pub can_reuse_downstream: bool,
514+
}

0 commit comments

Comments
 (0)