{ // A backend 304 validates the stored representation. Merge the new // metadata into the cached object and answer the client's original // condition locally, just like Varnish's conditional delivery path. if response.status() == StatusCode::NOT_MODIFIED && revalidation_sent { if let (Some(ticket), Some(cached)) = (cache_ticket.as_ref(), cached_revalidation.as_ref()) { let merged = cached.merge_validation_headers(response.headers()); let client_not_modified = crate::cache::conditional_not_modified(&client_conditionals, &merged); let mut revalidated = if let Some(mut hit) = state.cache.publish_revalidated( ticket.clone(), cached, merged.clone(), &origin_validation_headers, cache_settings, upstream_started.elapsed(), ) { if !client_not_modified { if let crate::cache::RangeSelection::Single(range) = range_selection { hit = hit.apply_range(range, if_range.as_ref()); } } let resident_age = hit.resident_age_seconds; let mut response = hit.response; cache_settings.annotate_reason( response.headers_mut(), "HIT", hit.hits, hit.remaining_ttl, "cache_revalidated", ); cache_settings .annotate_transformed(response.headers_mut(), hit.transformed); if hit.transformed { state.transform_metrics.record_cache_hit(scope); } cache_settings.annotate_resident_age(response.headers_mut(), resident_age); Some(response) } else if state.cache.ticket_current(ticket) { // The validation succeeded but the refreshed representation is no // longer eligible for shared storage (for example new no-store). // Serve it once, then discard the stored copy. state.cache.discard(ticket); let mut response = if !client_not_modified { if let crate::cache::RangeSelection::Single(range) = range_selection { cached.response_with_range(merged, range, if_range.as_ref()) } else { cached.response(merged) } } else { cached.response(merged) }; cache_settings.annotate_reason( response.headers_mut(), "HIT", 0, 0, "cache_revalidated_uncached", ); cache_settings .annotate_transformed(response.headers_mut(), cached.transformed()); if cached.transformed() { state.transform_metrics.record_cache_hit(scope); } Some(response) } else { None }; if let Some(mut revalidated) = revalidated.take() { if client_not_modified { revalidated = crate::cache::make_not_modified(revalidated); } revalidated.extensions_mut().insert(ResponseRulesApplied); revalidated .extensions_mut() .insert(OriginTiming(upstream_started.elapsed())); revalidated .extensions_mut() .insert(OriginBackend(active_backend.id.clone())); if let Some(exchange) = &exchange { exchange.cache_status.store(1, Ordering::Relaxed); *exchange .cache_reason .lock() .unwrap_or_else(|e| e.into_inner()) = "cache_revalidated"; } state.cache.record(scope, &host, "HIT", "cache_revalidated"); return intercept_route_origin_error(revalidated, route, &snapshot); } } } // Both origin and transformed policies must be cacheable; hiding a // Set-Cookie/private header never authorizes storage of private content. let transformed_plan = if origin_plan.is_none() || (ignore_static_cookie && crate::cache::varies_on(response.headers(), "cookie")) { None } else { crate::cache::plan( response.status(), response.headers(), cache_settings, upstream_started.elapsed(), ) }; let outcome = if cache_ticket.is_some() { "MISS" } else { "BYPASS" }; let reason = effective_reason .or(origin_reason) .or(revalidation_reason) .or(freshness_revalidation_reason) .unwrap_or_else(|| { if cache_ticket.is_some() && transformed_plan.is_none() { "response_headers_policy" } else if cache_ticket.is_some() { if force_cache { "cache_miss_forced" } else { "cache_miss" } } else { "configuration_changed" } }); if outcome == "MISS" && !miss_recorded { state.cache.record(scope, &host, "MISS", reason); if let Some(exchange) = &exchange { exchange.cache_status.store(2, Ordering::Relaxed); } } else if outcome == "MISS" && reason != "cache_miss" { state.cache.record_reason(scope, &host, reason); } if let Some(exchange) = &exchange { *exchange .cache_reason .lock() .unwrap_or_else(|e| e.into_inner()) = reason; } if let (Some(ticket), Some(plan), Some(transformed)) = (cache_ticket, origin_plan, transformed_plan) { let plan = if response .extensions() .get::() .is_some() { plan.without_vary("accept-encoding") } else { plan }; let plan = plan.restrict(transformed); let proactive_remaining_ttl = plan.remaining_ttl(); let proactive_ticket = ticket.clone(); response = state.cache.wrap_with_fill( response, ticket, plan, cache_settings, fill_leader.take(), ); if cache_settings.proactive_refresh_enabled && proactive_remaining_ttl > 0 { schedule_proactive_refresh( CacheRefreshContext { state: state.clone(), snapshot: snapshot.clone(), backend: active_backend.clone(), cache_settings: cache_settings.clone(), transform: transform_settings.cloned(), uri: active_uri.clone(), public_suffix: public_suffix.clone(), host: host.clone(), path: path.clone(), scope: scope.to_owned(), timeout: frontend .and_then(|frontend| frontend.proxy_timeouts.response_header_timeout()) .unwrap_or(state.header_timeout), local: local.0, tls, base_headers: proactive_base_headers.clone(), }, proactive_ticket, proactive_remaining_ttl, ); } } let transformed = response .extensions() .get::() .is_some() || response .extensions() .get::() .is_some(); cache_settings.annotate_reason(response.headers_mut(), outcome, 0, 0, reason); cache_settings.annotate_transformed(response.headers_mut(), transformed); response }