39ade0102a
Run 544 failed because the /api/dictionary/* nginx cache had been
poisoned with the upstream WAF's HTML block page (HTTP 200 + text/html,
"Доступ к сайту временно ограничен"). The previous pre-warm step only
checked %{http_code}, so the WAF response looked valid and got cached
for the full 6h TTL — every subsequent SSR render then resolved city
names via that HTML, breadcrumbs showed raw IATA codes, and 7 schedule
e2e specs failed.
Three changes that together close this hole:
1. ci-deploy pre-warm: two-step warm with body validation. Step 1 is
a cache-bust query (?_=ns timestamp) that proves upstream is healthy
independent of nginx cache. Step 2 fetches the canonical URL and
validates the response is JSON (starts with [/{ and is >1KB). If
the canonical body is HTML, retry once with `Cache-Control:
no-cache` to force a fresh upstream fetch (works once the matching
nginx config below is deployed); if still HTML, fail loudly with a
manual-purge instruction so the operator can rm the cache files.
2. nginx /api/dictionary/ location: add `proxy_cache_bypass
$http_cache_control` so the CI workflow can force-refresh on demand,
and `proxy_no_cache $no_cache_html` so HTML responses are never
stored in the first place.
3. flights-api-cache.conf: add `map $upstream_http_content_type
$no_cache_html` that flips to "1" when upstream returns text/html.
Drives the `proxy_no_cache` filter above.
Note: the nginx changes only take effect after setup-pve201.sh is
re-run on pve-201. Until then, any cache poisoning still stays poisoned
until the 6h TTL expires (or manual purge).
27 lines
1.2 KiB
Plaintext
27 lines
1.2 KiB
Plaintext
# Cache zone for ui-dashboard.gnerim.ru /api/* and /map/api/* upstreams.
|
|
# Lives in /etc/nginx/conf.d/ because proxy_cache_path is only valid in the
|
|
# http {} context, not inside server {}.
|
|
#
|
|
# Why we need it: flights.test.aeroflot.ru's WAF has a per-source-IP rate
|
|
# limit (~25-30 fresh TCP connections per window) that the parallel e2e
|
|
# burst trips. Caching read-only GETs by the customer-facing nginx layer
|
|
# absorbs the burst — only one request per (URI, window) reaches the WAF.
|
|
|
|
proxy_cache_path /var/cache/nginx/flights-api
|
|
levels=1:2
|
|
keys_zone=flights_api:10m
|
|
max_size=200m
|
|
inactive=30m
|
|
use_temp_path=off;
|
|
|
|
# Don't cache upstream responses whose Content-Type is HTML — the upstream
|
|
# WAF returns its block page ("Доступ к сайту временно ограничен") with
|
|
# HTTP 200 + text/html, and prior to this filter nginx happily cached that
|
|
# as a valid 200 for the next 6h, poisoning every subsequent dictionary
|
|
# read for the SSR app. Pair with `proxy_no_cache $no_cache_html;` in the
|
|
# server block.
|
|
map $upstream_http_content_type $no_cache_html {
|
|
~*text/html 1;
|
|
default "";
|
|
}
|