diff --git a/deploy/varnish/default.vcl.template b/deploy/varnish/default.vcl.template index e797693..ff31860 100644 --- a/deploy/varnish/default.vcl.template +++ b/deploy/varnish/default.vcl.template @@ -6,7 +6,7 @@ import std; # ===== Backend ===== backend app { .host = "app"; - .port = "${APP_PORT}"; + .port = "8000"; } # ===== ACL ===== @@ -28,27 +28,19 @@ sub vcl_recv { return (purge); } + # omijamy cache dla healthchecków / wewnętrznych nagłówków + if (req.url == "/healthcheck" || req.http.X-Internal-Check) { return (pass); } + + # Specjalna obsługa WebSocket i socket.io if (req.http.Upgrade ~ "(?i)websocket" || req.url ~ "^/socket.io/") { return (pipe); } - # omijamy cache dla healthchecków / wewnętrznych nagłówków - if (req.url == "/healthcheck" || req.http.X-Internal-Check) { - set req.http.X-Pass-Reason = "internal"; - return (pass); - } - # metody inne niż GET/HEAD bez cache - if (req.method != "GET" && req.method != "HEAD") { - set req.http.X-Pass-Reason = "method"; - return (pass); - } + if (req.method != "GET" && req.method != "HEAD") { return (pass); } # Żądania z Authorization nie są buforowane - if (req.http.Authorization) { - set req.http.X-Pass-Reason = "auth"; - return (pass); - } + if (req.http.Authorization) { return (pass); } # ---- Normalizacja Accept-Encoding (kolejność: zstd > br > gzip) ---- if (req.http.Accept-Encoding) { @@ -63,8 +55,17 @@ sub vcl_recv { } } + # ---- (Opcjonalnie) Normalizacja Accept dla obrazów generowanych wariantowo ---- + # if (req.url ~ "\.(png|jpe?g|gif|bmp)$") { + # if (req.http.Accept ~ "image/webp") { + # set req.http.X-Accept-Image = "modern"; # webp + # } else { + # set req.http.X-Accept-Image = "legacy"; # jpg/png + # } + # } + # ---- STATYCZNE – agresywny cache + ignorujemy sesję ---- - if (req.url ~ "^/static/" || req.url ~ "\\.(css|js|png|jpe?g|webp|svg|ico|woff2?)$") { + if (req.url ~ "^/static/" || req.url ~ "\.(css|js|png|jpe?g|webp|svg|ico|woff2?)$") { unset req.http.Cookie; unset req.http.Authorization; return (hash); @@ -74,6 +75,27 @@ sub vcl_recv { set req.http.X-Forwarded-Proto = "https"; } + if (req.url == "/healthcheck" || req.http.X-Internal-Check) { + set req.http.X-Pass-Reason = "internal"; + return (pass); + } + + if (req.method != "GET" && req.method != "HEAD") { + set req.http.X-Pass-Reason = "method"; + return (pass); + } + + if (req.http.Authorization) { + set req.http.X-Pass-Reason = "auth"; + return (pass); + } + + # jeśli chcesz PASS przy cookie: + # if (req.http.Cookie) { + # set req.http.X-Pass-Reason = "cookie"; + # return (pass); + # } + return (hash); } @@ -83,17 +105,20 @@ sub vcl_pipe { set bereq.http.Upgrade = req.http.Upgrade; set bereq.http.Connection = req.http.Connection; } - set bereq.http.connection = "close"; - set bereq.http.Ping-Interval = "25s"; - set bereq.http.Ping-Timeout = "60s"; } # ===== HASH ===== sub vcl_hash { hash_data(req.url); if (req.http.host) { hash_data(req.http.host); } else { hash_data(server.ip); } + + # Cookie: zostają dla dynamicznych (dla statyków wyczyszczone wcześniej) if (req.http.Cookie) { hash_data(req.http.Cookie); } + + # Accept-Encoding: już znormalizowany do zstd/br/gzip/identity if (req.http.Accept-Encoding) { hash_data(req.http.Accept-Encoding); } + + # (Opcjonalnie) sygnał obrazów z negocjacją po Accept if (req.http.X-Accept-Image) { hash_data(req.http.X-Accept-Image); } } @@ -107,7 +132,7 @@ sub vcl_backend_response { return (deliver); } - # NIE cache'uj redirectów + # NIE cache'uj redirectów do loginu (HTML) z backendu if (beresp.status >= 300 && beresp.status < 400) { set beresp.uncacheable = true; set beresp.ttl = 0s; @@ -115,8 +140,9 @@ sub vcl_backend_response { return (deliver); } - # Nie cache'uj statyków ≠200 - if (bereq.url ~ "^/static/" || bereq.url ~ "\\.(css|js|png|jpe?g|webp|svg|ico|woff2?)($|\\?)") { + # Nie cache'uj statyków, jeśli status ≠ 200 + if (bereq.url ~ "^/static/" || + bereq.url ~ "\.(css|js|png|jpe?g|webp|svg|ico|woff2?)($|\?)") { if (beresp.status != 200) { set beresp.uncacheable = true; set beresp.ttl = 0s; @@ -124,54 +150,39 @@ sub vcl_backend_response { } } - # JS/CSS z HTML → nie cache - if (bereq.url ~ "\\.js(\\?.*)?$" && beresp.http.Content-Type ~ "(?i)text/html") { + # Jeśli pod .js przychodzi text/html — też nie cache'uj (to zwykle redirect/login) + if (bereq.url ~ "\.js(\?.*)?$" && beresp.http.Content-Type ~ "(?i)text/html") { set beresp.uncacheable = true; set beresp.ttl = 0s; return (deliver); } - # Wymuś Content-Type - if (bereq.url ~ "\\.js(\\?.*)?$") { + # Wymuś poprawny Content-Type dla .js/.css, gdy backend zwróci HTML + if (bereq.url ~ "\.js(\?.*)?$") { if (!beresp.http.Content-Type || beresp.http.Content-Type ~ "(?i)text/html") { set beresp.http.Content-Type = "application/javascript; charset=utf-8"; } } - if (bereq.url ~ "\\.css(\\?.*)?$") { + if (bereq.url ~ "\.css(\?.*)?$") { if (!beresp.http.Content-Type || beresp.http.Content-Type ~ "(?i)text/html") { set beresp.http.Content-Type = "text/css; charset=utf-8"; } } - # ---- PARSONANIE TTL (PRZED STATIC!) ---- - if (beresp.http.Cache-Control ~ "(?i)s-maxage=([0-9]+)") { - set beresp.ttl = std.duration(regsub(beresp.http.Cache-Control, "(?i).*s-maxage=([0-9]+).*", "\\1") + "s", 0s); - } else if (beresp.http.Cache-Control ~ "(?i)max-age=([0-9]+)") { - set beresp.ttl = std.duration(regsub(beresp.http.Cache-Control, "(?i).*max-age=([0-9]+).*", "\\1") + "s", 0s); - } else if (beresp.http.Expires) { - set beresp.ttl = std.time(beresp.http.Expires, now) - now; - if (beresp.ttl < 0s) { set beresp.ttl = 0s; } - } else { - if (beresp.ttl <= 0s) { set beresp.ttl = 60s; } - } - - # ---- STATYCZNE (ulepszone Vary) ---- - if (bereq.url ~ "^/static/" || bereq.url ~ "\\.(css|js|png|jpe?g|webp|svg|ico|woff2?)$") { + # ---- STATYCZNE: zdejmij Set-Cookie i Vary: Cookie, zapewnij TTL ---- + if (bereq.url ~ "^/static/" || bereq.url ~ "\.(css|js|png|jpe?g|webp|svg|ico|woff2?)$") { unset beresp.http.Set-Cookie; - # CZYSZCZENIE VARY: usuwa \1 Cookie, Accept-Encoding + # Jeśli backend dodał Vary: Cookie, usuńmy ten element (nie wpływa na statyki) if (beresp.http.Vary) { - set beresp.http.Vary = regsuball(beresp.http.Vary, "\\\\1[[:space:]]*(,|$)", ","); - set beresp.http.Vary = regsuball(beresp.http.Vary, "(?i)(^|,)[[:space:]]*(Cookie|Accept-Encoding)[[:space:]]*(,|$)", ","); + set beresp.http.Vary = regsuball(beresp.http.Vary, "(?i)(^|,)[[:space:]]*Cookie[[:space:]]*(,|$)", "\1"); set beresp.http.Vary = regsuball(beresp.http.Vary, ",[[:space:]]*,", ","); set beresp.http.Vary = regsub(beresp.http.Vary, "^[[:space:]]*,[[:space:]]*", ""); set beresp.http.Vary = regsub(beresp.http.Vary, "[[:space:]]*,[[:space:]]*$", ""); - if (beresp.http.Vary ~ "^[[:space:]]*$|^$") { - unset beresp.http.Vary; - } + if (beresp.http.Vary ~ "^[[:space:]]*$") { unset beresp.http.Vary; } } - # TTL TYLKO jeśli backend NIE ma s-maxage/max-age + # Jeśli brak kontroli czasu życia – ustawiamy twarde wartości if (!(beresp.http.Cache-Control ~ "(?i)(s-maxage|max-age)")) { set beresp.ttl = 24h; set beresp.http.Cache-Control = "public, max-age=86400, immutable"; @@ -181,25 +192,40 @@ sub vcl_backend_response { set beresp.keep = 24h; } - # Immutable + # ---- Ogólne TTL z nagłówków ---- + if (beresp.http.Cache-Control ~ "(?i)s-maxage=([0-9]+)") { + set beresp.ttl = std.duration(regsub(beresp.http.Cache-Control, "(?i).*s-maxage=([0-9]+).*", "\1") + "s", 0s); + } else if (beresp.http.Cache-Control ~ "(?i)max-age=([0-9]+)") { + set beresp.ttl = std.duration(regsub(beresp.http.Cache-Control, "(?i).*max-age=([0-9]+).*", "\1") + "s", 0s); + } else if (beresp.http.Expires) { + set beresp.ttl = std.time(beresp.http.Expires, now) - now; + if (beresp.ttl < 0s) { set beresp.ttl = 0s; } + } else { + if (beresp.ttl <= 0s) { set beresp.ttl = 60s; } + } + + # Immutable => dłuższe grace/keep if (beresp.http.Cache-Control ~ "(?i)immutable") { set beresp.grace = 1h; set beresp.keep = 24h; } - # Kompresja + # Kompresja po stronie Varnisha wyłącznie dla klientów akceptujących gzip + # i tylko jeśli backend nie dostarczył już Content-Encoding. if (!beresp.http.Content-Encoding && bereq.http.Accept-Encoding ~ "gzip") { + # Kompresujemy tylko „tekstowe” typy; wykluczamy WASM if (beresp.http.Content-Type ~ "(?i)text/|application/(javascript|json|xml)") { set beresp.do_gzip = true; } } - # Stream duże + # Duże odpowiedzi streamujemy if (beresp.http.Content-Length && std.integer(beresp.http.Content-Length, 0) > 1048576) { set beresp.do_stream = true; } } +# (Opcjonalnie) Serwuj „stale” przy błędach backendu, jeśli jest obiekt w grace sub vcl_backend_error { return (deliver); } @@ -209,7 +235,7 @@ sub vcl_deliver { if (obj.uncacheable) { if (req.http.X-Pass-Reason) { set resp.http.X-Cache = "PASS:" + req.http.X-Pass-Reason; - } else if (resp.http.X-Pass-Reason) { + } else if (resp.http.X-Pass-Reason) { # z backendu set resp.http.X-Cache = "PASS:" + resp.http.X-Pass-Reason; } else { set resp.http.X-Cache = "PASS"; @@ -228,28 +254,7 @@ sub vcl_deliver { unset resp.http.Server; } -# ===== SYNTH ===== sub vcl_synth { - unset resp.http.X-Varnish; - - if (resp.status == 429) { - set resp.http.Content-Type = "text/html; charset=utf-8"; - synthetic({"
Limit: 200 req / 10 s