1414use axum:: response:: IntoResponse ;
1515use bytes:: Bytes ;
1616use hyper:: client:: HttpConnector ;
17+ use hyper_tls:: HttpsConnector ;
18+ type HttpsClient = Client < HttpsConnector < HttpConnector > > ;
1719use hyper:: { Body , Client , Request , Response } ;
1820use once_cell:: sync:: Lazy ;
1921use sha2:: { Digest , Sha256 } ;
@@ -47,10 +49,12 @@ pub static SEMAPHORE: Lazy<Arc<Semaphore>> =
4749 Lazy :: new ( || Arc :: new ( Semaphore :: new ( * MAX_CONCURRENT_REQUESTS ) ) ) ;
4850
4951/// Shared HTTP client for all outbound requests
50- static HTTP_CLIENT : Lazy < Client < HttpConnector > > = Lazy :: new ( Client :: new) ;
52+ static HTTP_CLIENT : Lazy < HttpsClient > = Lazy :: new ( || {
53+ let https = HttpsConnector :: new ( ) ;
54+ Client :: builder ( ) . build :: < _ , Body > ( https)
55+ } ) ;
5156
5257/// Background task that persistently writes cache entries to the configured backend
53-
5458static CACHE_WRITER : Lazy < mpsc:: Sender < ( String , Bytes , Vec < ( String , String ) > ) > > = Lazy :: new ( || {
5559 let ( tx, mut rx) = mpsc:: channel :: < ( String , Bytes , Vec < ( String , String ) > ) > ( 100 ) ;
5660 tokio:: spawn ( async move {
@@ -82,6 +86,11 @@ pub async fn proxy_handler(req: Request<Body>) -> impl IntoResponse {
8286 let uri = req. uri ( ) . to_string ( ) ;
8387 tracing:: debug!( "🔗 Received request for URI: {}" , uri) ;
8488
89+ tracing:: debug!( "🔎 Incoming request headers:" ) ;
90+ for ( k, v) in req. headers ( ) . iter ( ) {
91+ tracing:: debug!( " {}: {:?}" , k, v) ;
92+ }
93+
8594 // Increment total request counter for each URI
8695 counter ! ( "cachebolt_proxy_requests_total" , "uri" => uri. clone( ) ) . increment ( 1 ) ;
8796
@@ -169,9 +178,11 @@ pub async fn proxy_handler(req: Request<Body>) -> impl IntoResponse {
169178 }
170179
171180 // Split response into parts
172- let ( parts, body) = resp. into_parts ( ) ;
181+ let ( mut parts, body) = resp. into_parts ( ) ;
173182 let body_bytes = hyper:: body:: to_bytes ( body) . await . unwrap_or_default ( ) ;
174183
184+ parts. headers . remove ( "content-length" ) ;
185+
175186 let headers_vec = parts
176187 . headers
177188 . iter ( )
@@ -210,7 +221,10 @@ pub async fn proxy_handler(req: Request<Body>) -> impl IntoResponse {
210221 ) ;
211222 }
212223 } else {
213- tracing:: info!( "⏩ Cache bypass activated for '{}' due to client header" , uri) ;
224+ tracing:: info!(
225+ "⏩ Cache bypass activated for '{}' due to client header" ,
226+ uri
227+ ) ;
214228 }
215229
216230 Response :: from_parts ( parts, Body :: from ( body_bytes) )
@@ -304,18 +318,58 @@ pub fn hash_uri(uri: &str) -> String {
304318}
305319
306320/// Sends an outbound GET request to the downstream backend
321+ /// Sends an outbound GET request to the downstream backend, forwarding all headers except 'accept-encoding'.
322+ /// This prevents curl: (52) Empty reply from server errors caused by unsupported encodings.
323+ ///
324+ /// # Arguments
325+ /// - `uri`: The path to append to the downstream base URL.
326+ /// - `original_req`: The incoming Axum request, from which headers are forwarded.
327+ ///
328+ /// # Returns
329+ /// - `Ok(Response)` with the downstream response if successful.
330+ /// - `Err(())` if the downstream call fails or the request could not be built.
307331pub async fn forward_request ( uri : & str , original_req : Request < Body > ) -> Result < Response < Body > , ( ) > {
332+ // Get the config and build the downstream full URL
308333 let cfg = CONFIG . get ( ) . unwrap ( ) ;
309334 let full_url = format ! ( "{}{}" , cfg. downstream_base_url, uri) ;
310335
336+ // Debug: Log the scheme, host, and path of the downstream URL
337+ if let Ok ( parsed_url) = url:: Url :: parse ( & full_url) {
338+ tracing:: info!(
339+ "🌐 Downstream request: scheme='{}' host='{}' path='{}'" ,
340+ parsed_url. scheme( ) ,
341+ parsed_url. host_str( ) . unwrap_or( "" ) ,
342+ parsed_url. path( )
343+ ) ;
344+ }
345+
346+ // Parse downstream_base_url to extract the host (domain)
347+ let downstream_host = url:: Url :: parse ( & cfg. downstream_base_url )
348+ . ok ( )
349+ . and_then ( |u| u. host_str ( ) . map ( |s| s. to_string ( ) ) )
350+ . unwrap_or_else ( || "" . to_string ( ) ) ;
351+
352+ // Build the request, starting with the URL and GET method
311353 let mut builder = Request :: builder ( ) . uri ( full_url. clone ( ) ) . method ( "GET" ) ;
312354
313- // Clone headers from the original request
355+ // Copy all headers from the incoming request,
356+ // except for 'accept-encoding' and 'host'
357+ // (We want to control the Host header for SNI/proxying, and avoid content-encoding issues.)
314358 for ( key, value) in original_req. headers ( ) . iter ( ) {
359+ if key. as_str ( ) . eq_ignore_ascii_case ( "accept-encoding" )
360+ || key. as_str ( ) . eq_ignore_ascii_case ( "host" )
361+ {
362+ continue ;
363+ }
315364 builder = builder. header ( key, value) ;
316365 }
317366
318- // Build the request
367+ // Inject the Host header, if it was successfully extracted from the downstream_base_url
368+ if !downstream_host. is_empty ( ) {
369+ builder = builder. header ( "Host" , downstream_host) ;
370+ }
371+
372+ // Build the final request object with empty body
319373 let req = match builder. body ( Body :: empty ( ) ) {
320374 Ok ( req) => req,
321375 Err ( e) => {
@@ -324,7 +378,7 @@ pub async fn forward_request(uri: &str, original_req: Request<Body>) -> Result<R
324378 }
325379 } ;
326380
327- // Make the request without timeout
381+ // Send the HTTP request to the downstream service
328382 match HTTP_CLIENT . request ( req) . await {
329383 Ok ( resp) => Ok ( resp) ,
330384 Err ( e) => {
0 commit comments