@@ -49,6 +49,15 @@ impl Default for RequestCtx {
4949 }
5050}
5151
52+ impl Drop for RequestCtx {
53+ fn drop ( & mut self ) {
54+ // Ensure active connections are decremented when context is dropped
55+ if let Some ( upstream) = & self . upstream {
56+ upstream. dec_connections ( ) ;
57+ }
58+ }
59+ }
60+
5261/// Mutable state for hot reloading
5362#[ derive( Clone ) ]
5463pub struct ProxyState {
@@ -480,7 +489,7 @@ impl ProxyHttp for PingclairProxy {
480489
481490 // Lookup token in challenge handler
482491 let handler = manager. challenge_handler ( ) ;
483- if let Some ( key_auth) = handler. get_token ( token) . await {
492+ if let Some ( key_auth) = handler. get_token ( token) {
484493 tracing:: info!( "π Serving ACME challenge for token: {}" , token) ;
485494
486495 let mut header = pingora_http:: ResponseHeader :: build ( 200 , Some ( 2 ) ) . unwrap ( ) ;
@@ -622,23 +631,50 @@ impl ProxyHttp for PingclairProxy {
622631 let state = ctx. state . as_ref ( ) . unwrap ( ) ;
623632 if let Some ( upstream) = self . select_upstream ( state, route_idx, client_ip. as_deref ( ) ) {
624633 ctx. upstream = Some ( upstream. clone ( ) ) ;
625-
634+
626635 // Track active connections
627636 upstream. inc_connections ( ) ;
628-
629- // Get proxy config for headers
637+
638+ // Get proxy config for headers and timeouts
639+ let mut read_timeout_ms = None ;
640+ let mut write_timeout_ms = None ;
641+
630642 if let Some ( proxy_config) = self . get_proxy_config ( state, route_idx) {
631643 ctx. headers_up = proxy_config. headers_up . clone ( ) ;
632644 ctx. headers_down = proxy_config. headers_down . clone ( ) ;
645+ read_timeout_ms = proxy_config. read_timeout ;
646+ write_timeout_ms = proxy_config. write_timeout ;
633647 }
634-
648+
635649 // Parse and create peer
636650 if let Some ( ( host, port, tls) ) = Self :: parse_upstream ( & upstream. addr ) {
637- let peer = HttpPeer :: new (
651+ let mut peer = HttpPeer :: new (
638652 ( host. as_str ( ) , port) ,
639653 tls,
640654 host. clone ( ) ,
641655 ) ;
656+
657+ // Apply timeouts if configured
658+ if let Some ( read_timeout) = read_timeout_ms {
659+ if read_timeout > 0 {
660+ peer. options . read_timeout = Some ( std:: time:: Duration :: from_millis ( read_timeout as u64 ) ) ;
661+ tracing:: debug!( "β±οΈ Applied read timeout: {}ms for {}" , read_timeout, host) ;
662+ }
663+ }
664+
665+ if let Some ( write_timeout) = write_timeout_ms {
666+ if write_timeout > 0 {
667+ peer. options . write_timeout = Some ( std:: time:: Duration :: from_millis ( write_timeout as u64 ) ) ;
668+ tracing:: debug!( "β±οΈ Applied write timeout: {}ms for {}" , write_timeout, host) ;
669+ }
670+ }
671+
672+ // Set default connection timeout (10 seconds) if not configured
673+ if peer. options . connection_timeout . is_none ( ) {
674+ peer. options . connection_timeout = Some ( std:: time:: Duration :: from_secs ( 10 ) ) ;
675+ tracing:: debug!( "β±οΈ Applied default connection timeout: 10s for {}" , host) ;
676+ }
677+
642678 return Ok ( Box :: new ( peer) ) ;
643679 }
644680 }
@@ -679,11 +715,6 @@ impl ProxyHttp for PingclairProxy {
679715 where
680716 Self :: CTX : Send + Sync ,
681717 {
682- // Decrement active connections
683- if let Some ( upstream) = & ctx. upstream {
684- upstream. dec_connections ( ) ;
685- }
686-
687718 // Add configured downstream headers
688719 for ( key, value) in & ctx. headers_down {
689720 upstream_response. insert_header ( key. clone ( ) , value. as_str ( ) ) ?;
@@ -692,9 +723,36 @@ impl ProxyHttp for PingclairProxy {
692723 // Add server identification headers
693724 upstream_response. insert_header ( "Server" , "Pingclair" ) ?;
694725
695- // Add security headers
696- upstream_response. insert_header ( "X-Content-Type-Options" , "nosniff" ) ?;
697- upstream_response. insert_header ( "X-Frame-Options" , "DENY" ) ?;
726+ // Add security headers based on configuration
727+ if let Some ( state) = & ctx. state {
728+ if state. config . security . enabled {
729+ // Basic security headers
730+ upstream_response. insert_header ( "X-Content-Type-Options" , & state. config . security . x_content_type_options ) ?;
731+ upstream_response. insert_header ( "X-Frame-Options" , & state. config . security . x_frame_options ) ?;
732+ upstream_response. insert_header ( "X-XSS-Protection" , & state. config . security . x_xss_protection ) ?;
733+ upstream_response. insert_header ( "X-Permitted-Cross-Domain-Policies" , & state. config . security . x_permitted_cross_domain ) ?;
734+ upstream_response. insert_header ( "Referrer-Policy" , & state. config . security . referrer_policy ) ?;
735+ upstream_response. insert_header ( "Permissions-Policy" , & state. config . security . permissions_policy ) ?;
736+
737+ // HSTS header if TLS is used and HSTS is configured
738+ if state. config . tls . as_ref ( ) . map_or ( false , |tls| tls. auto || tls. cert . is_some ( ) ) {
739+ if let Some ( ref hsts_config) = state. config . security . hsts {
740+ let hsts_value = format ! (
741+ "max-age={};{}{}" ,
742+ hsts_config. max_age,
743+ if hsts_config. include_subdomains { " includeSubDomains;" } else { "" } ,
744+ if hsts_config. preload { " preload" } else { "" }
745+ ) ;
746+ upstream_response. insert_header ( "Strict-Transport-Security" , & hsts_value) ?;
747+ }
748+ }
749+
750+ // CSP header if configured
751+ if let Some ( ref csp) = state. config . security . csp {
752+ upstream_response. insert_header ( "Content-Security-Policy" , csp) ?;
753+ }
754+ }
755+ }
698756
699757 // Log request timing (only in debug or non-benchmark)
700758 let elapsed = ctx. start_time . elapsed ( ) ;
@@ -717,11 +775,6 @@ impl ProxyHttp for PingclairProxy {
717775 ctx : & mut Self :: CTX ,
718776 _client_reused : bool ,
719777 ) -> Box < pingora_core:: Error > {
720- // Decrement active connections
721- if let Some ( upstream) = & ctx. upstream {
722- upstream. dec_connections ( ) ;
723- }
724-
725778 let elapsed = ctx. start_time . elapsed ( ) ;
726779 tracing:: error!(
727780 peer = %peer,
0 commit comments