{ if handshake.is_some() { return error(StatusCode::BAD_REQUEST, "RustProxy: Web Server source does not support WebSocket upgrades\n"); } let service_path = match route.rewritten_path(&path) { Ok(Some(value)) => value, Ok(None) => path.as_ref().to_owned(), Err(_) => return error(StatusCode::BAD_REQUEST, "RustProxy: invalid Web Server rewrite path\n"), }; #[cfg(target_os = "linux")] let kernel_sendfile = request.extensions() .get::().cloned(); #[cfg(target_os = "linux")] let identity_request = request.version() == http::Version::HTTP_11 && !request.headers().contains_key("accept-encoding") && !request.headers().contains_key("range") && !request.headers().contains_key("if-range"); let cache_settings = route.frontend.as_ref() .map(|frontend| &frontend.cache) .unwrap_or(&snapshot.envelope.config.cache); let transform_settings = route.frontend.as_ref().map(|frontend| &frontend.transform); let scope = route.frontend.as_ref().map(|frontend| frontend.id.as_str()).unwrap_or(""); let enabled = cache_settings.active_for(route.cache) && frontend.is_none_or(|frontend| frontend.basic_auth.is_none() && frontend.tinyauth.is_none()); if let Some(exchange) = request.extensions().get::>() { exchange.set_cache_enabled(enabled); exchange.set_transform_enabled(transform_settings.is_some_and(|settings| settings.enabled)); } request.headers_mut().insert(HOST, original_host.clone()); let request_method = request.method().clone(); let mut request_headers = std::mem::take(request.headers_mut()); let request_uri = request.uri().clone(); let request_version = request.version(); let public_suffix = request_uri.path_and_query() .map(|item| item.as_str()).unwrap_or("/").to_owned(); let empty_body = if enabled && (request_method == Method::GET || request_method == Method::HEAD) { match empty_for_cache(&mut request).await { Ok(empty) => empty, Err(_) => return error(StatusCode::BAD_REQUEST, "RustProxy: invalid or incomplete request body\n"), } } else { enabled && request.body().is_end_stream() }; let cache_plan = cache_plan::CachePlan::new( (!enabled).then_some("cache_disabled"), cache_settings, &path, || route.frontend.as_ref().is_some_and(|frontend| frontend.force_cache.matches(&path, &public_suffix)), ); let force_cache = cache_plan.force_cache(); let ignore_cookie = cache_plan.ignore_cookie(); let policy = cache_plan.request_policy( &request_method, &request_headers, empty_body, &path, request_uri.query().is_some(), cache_settings, ); let eligible = enabled && policy.bypass_reason().is_none(); let key_headers = if eligible && ignore_cookie && request_headers.contains_key("cookie") { let mut headers = request_headers.clone(); headers.remove("cookie"); Some(headers) } else { None }; let cache_ticket = if eligible { state.cache.ticket_scoped_for_config(snapshot.envelope.revision, state.cache.revision(), scope, context.scheme, original_host.to_str().unwrap_or(""), &route.id, &public_suffix, key_headers.as_ref().unwrap_or(&request_headers)) } else { None }; let range = cache_plan::range_selection(eligible, &request_method, &request_headers); let if_range = if eligible { request_headers.get("if-range").cloned() } else { None }; let range_requested = !matches!(range, crate::cache::RangeSelection::None); let cached_revalidation = if let Some(ticket) = cache_ticket.as_ref() { state.cache.revalidation_tiered(ticket).await } else { None }; let needs_validation = policy.revalidation_reason().is_some() || cached_revalidation.as_ref().is_some_and(|cached| !cached.reusable_for_request(&request_headers)); if let Some(ticket) = &cache_ticket { if !needs_validation { if let Some(mut hit) = state.cache.get_tiered(ticket, cache_settings).await { if hit.stale { } else { let conditional = crate::cache::conditional_not_modified( &request_headers, hit.response.headers()); if !conditional { if let crate::cache::RangeSelection::Single(requested) = range { hit = hit.apply_range(requested, if_range.as_ref()); } } let mut response = if conditional { crate::cache::make_not_modified(hit.response) } else { hit.response }; if request_method == Method::HEAD { *response.body_mut() = Body::empty(); } cache_settings.annotate_reason(response.headers_mut(), "HIT", hit.hits, hit.remaining_ttl, "cache_hit"); cache_settings.annotate_transformed(response.headers_mut(), hit.transformed); cache_settings.annotate_resident_age(response.headers_mut(), hit.resident_age_seconds); state.cache.record(scope, &host, "HIT", "cache_hit"); if hit.transformed { state.transform_metrics.record_cache_hit(scope); } if let Some(exchange) = request.extensions().get::>() { exchange.cache_status.store(1, Ordering::Relaxed); *exchange.cache_reason.lock().unwrap_or_else(|e| e.into_inner()) = "cache_hit"; } response.extensions_mut().insert(ResponseRulesApplied); response.extensions_mut().insert(StaticSourceResponse); return intercept_route_origin_error(response, route, &snapshot); } } } } if cache_ticket.is_some() && crate::cache::only_if_cached(&request_headers) { return error(StatusCode::GATEWAY_TIMEOUT, "RustProxy: only-if-cached request has no usable Web Server representation\n"); } let static_body = std::mem::replace(request.body_mut(), Body::empty()); let (origin_request_headers, revalidation_sent) = if cache_ticket.is_some() { web_cache_revalidation::web_origin_request_headers( &request_headers, cached_revalidation.as_ref()) } else { (std::mem::take(&mut request_headers), false) }; let started = Instant::now(); let response = crate::web_server::serve(service, request_method.clone(), origin_request_headers, request_uri, &service_path, static_body, crate::web_server::RequestContext { client_ip: context.client_ip, client_port: Some(peer.0.port()), server_name: host.clone(), server_port: local.0.port(), tls, version: request_version, }).await; if let Some(fallback) = execute_error_fallback( state.clone(), peer, local, &snapshot, &host, tls, route, response.status(), &fallback_request, &fallback_body, ).await { return fallback; } let mut response = intercept_route_origin_error(response, route, &snapshot); if revalidation_sent && response.status() == StatusCode::NOT_MODIFIED { if let (Some(ticket), Some(cached)) = (cache_ticket.as_ref(), cached_revalidation.as_ref()) { let revalidated = web_cache_revalidation::finish_web_revalidation( &state.cache, ticket, cached, response.headers(), cache_settings, started.elapsed(), &request_headers, &request_method, ignore_cookie, force_cache, |headers| apply_response_rules(headers, &snapshot, route.frontend.as_deref(), Some(route), false), ); if let Some(mut hit) = revalidated { state.cache.record(scope, &host, "HIT", hit.reason); if hit.transformed { state.transform_metrics.record_cache_hit(scope); } if let Some(exchange) = request.extensions().get::>() { exchange.cache_status.store(1, Ordering::Relaxed); *exchange.cache_reason.lock().unwrap_or_else(|e| e.into_inner()) = hit.reason; } hit.response.extensions_mut().insert(ResponseRulesApplied); hit.response.extensions_mut().insert(StaticSourceResponse); return intercept_route_origin_error(hit.response, route, &snapshot); } return error(StatusCode::SERVICE_UNAVAILABLE, "RustProxy: Web Server cache entry invalidated during revalidation\n"); } } if cached_revalidation.is_some() && response.status() != StatusCode::NOT_MODIFIED && response.status().as_u16() < 500 { if let Some(ticket) = cache_ticket.as_ref() { state.cache.discard(ticket); } } let origin_headers = response.headers(); let origin_safe = cache_ticket.is_some() && { let explicit_origin_policy = origin_headers.contains_key("cache-control") || origin_headers.contains_key("expires") || origin_headers.contains_key("set-cookie") || origin_headers.contains_key("vary"); !explicit_origin_policy || crate::cache::plan(response.status(), origin_headers, cache_settings, started.elapsed()).is_some() }; let no_transform = crate::transform::no_transform(origin_headers); let skip_compression = crate::compression::no_transform(origin_headers); let origin_validators = crate::transform::OriginValidators { etag: origin_headers.get("etag").cloned(), last_modified: origin_headers.get("last-modified").cloned(), }; if skip_compression || range_requested { response.extensions_mut().insert(crate::compression::Skip); } response.extensions_mut().insert(origin_validators); apply_response_rules(response.headers_mut(), &snapshot, route.frontend.as_deref(), Some(route), false); if response.extensions().get::().is_some() { response.headers_mut().insert("cache-control", HeaderValue::from_static("no-store")); } response.extensions_mut().insert(ResponseRulesApplied); response.extensions_mut().insert(StaticSourceResponse); let origin_eligible_plan = if origin_safe && !(ignore_cookie && crate::cache::varies_on(response.headers(), "cookie")) { crate::cache::plan(response.status(), response.headers(), cache_settings, started.elapsed()) } else { None }; if request_method == Method::GET && !range_requested { if let Some(settings) = transform_settings.filter(|settings| settings.enabled) { let transform_start = Instant::now(); response = match crate::transform::apply(response, &public_suffix, settings, no_transform).await { Ok(value) => { state.transform_metrics.record_response(scope, &value, transform_start.elapsed()); value } Err(cause) => { state.transform_metrics.record_error(scope, transform_start.elapsed()); tracing::debug!(error = %cause, "Web Server transform failed"); return error(StatusCode::BAD_GATEWAY, "RustProxy: Web Server response could not be transformed\n"); } }; } } let fill_plan = if origin_eligible_plan.is_some() { crate::cache::plan(response.status(), response.headers(), cache_settings, started.elapsed()) } else { None }; let reason = if !enabled { "cache_disabled" } else if let Some(reason) = policy.bypass_reason() { reason } else if needs_validation { "request_revalidated" } else if cache_ticket.is_some() && fill_plan.is_none() { "response_headers_policy" } else if cache_ticket.is_some() { "cache_miss" } else { "cache_unavailable" }; let fillable = request_method == Method::GET && !range_requested; let outcome = if cache_ticket.is_some() { "MISS" } else { "BYPASS" }; if let (Some(ticket), Some(origin), Some(transformed)) = ( cache_ticket, origin_eligible_plan, fill_plan, ) { if fillable { let canonical = response.extensions().get::().is_some(); let mut plan = if canonical { origin.without_vary("accept-encoding") } else { origin }; if force_cache { if request_headers.contains_key("cookie") { plan = plan.with_vary("cookie"); } if request_headers.contains_key("authorization") { plan = plan.with_vary("authorization"); } } response = state.cache.wrap_with_fill(response, ticket, plan.restrict(transformed), cache_settings, None); } } if let Some(exchange) = request.extensions().get::>() { exchange.cache_status.store(if outcome == "MISS" { 2 } else { 3 }, Ordering::Relaxed); *exchange.cache_reason.lock().unwrap_or_else(|e| e.into_inner()) = reason; } state.cache.record(scope, &host, outcome, reason); cache_settings.annotate_reason(response.headers_mut(), outcome, 0, 0, reason); let transformed = response.extensions().get::().is_some(); cache_settings.annotate_transformed(response.headers_mut(), transformed); #[cfg(target_os = "linux")] if !enabled && identity_request && request_method == Method::GET && !transform_settings.is_some_and(|settings| settings.enabled) && response.status() == StatusCode::OK && !response.headers().contains_key("content-encoding") && !response.headers().contains_key("transfer-encoding") && !response.headers().contains_key("content-range") { if let (Some(slots), Some(candidate)) = ( kernel_sendfile, response.extensions().get::().cloned(), ) { let correct_length = response.headers().get("content-length") .and_then(|v| v.to_str().ok()) .and_then(|v| v.parse::().ok()) == Some(candidate.length); if correct_length { if let Some(body) = slots.create_body(candidate, service.web_metrics.clone()) { *response.body_mut() = Body::new(body); } } } } response }