diff --git a/varnish/default.vcl b/varnish/default.vcl index ce888eb..61f5c2f 100644 --- a/varnish/default.vcl +++ b/varnish/default.vcl @@ -3,6 +3,7 @@ vcl 4.1; import std; import vsthrottle; +# Cache-Status zgodny z RFC 9211. Wymaga vmod_vsthrottle. backend default { .host = "plik"; .port = "8080"; @@ -26,11 +27,16 @@ acl purge { } sub vcl_recv { + # Wyczyść nieufne nagłówki diagnostyczne od klienta. unset req.http.X-Cache; unset req.http.X-Cache-Hits; + unset req.http.Cache-Status; + unset req.http.X-CS-Decision; - set req.http.Host = regsub(req.http.Host, ":[0-9]+", ""); - unset req.http.proxy; + if (req.http.Host) { + set req.http.Host = regsub(req.http.Host, ":[0-9]+$", ""); + } + unset req.http.Proxy; set req.backend_hint = default; set req.url = std.querysort(req.url); set req.url = regsub(req.url, "\?$", ""); @@ -58,43 +64,54 @@ sub vcl_recv { if (!client.ip ~ purge) { return (synth(405, "Not allowed.")); } + # Lokalny BAN zachowuje pierwotną semantykę wyrażenia regularnego. ban("req.http.host == " + req.http.host + " && req.url ~ " + req.url); return (synth(200, "Banned")); } - if (req.method != "GET" && req.method != "HEAD" && req.method != "OPTIONS") { + # OPTIONS nie jest cache'owalne; inne metody niż GET/HEAD też pomijamy. + if (req.method != "GET" && req.method != "HEAD") { + set req.http.X-CS-Decision = "method"; return (pass); } if (req.http.Authorization) { + set req.http.X-CS-Decision = "bypass"; return (pass); } - # upload / status / api / ajax poza cache + # Klient wymaga ponownej walidacji: bezpiecznie kierujemy do backendu. + if (req.http.Cache-Control ~ "(?i)(^|,)[[:space:]]*(no-cache|max-age=0)([[:space:]]*(,|$))" || + req.http.Pragma ~ "(?i)no-cache") { + set req.http.X-CS-Decision = "request"; + return (pass); + } + + # upload / status / api / ajax poza cache. if ( - req.url ~ "(?i)^/(upload|api|status|progress|session|login|logout)(/|$)" || + req.url ~ "(?i)^/(upload|api|status|progress|session|login|logout)(/|$|\?)" || req.url ~ "(?i)/(ajax|ahah)/" ) { + set req.http.X-CS-Decision = "bypass"; return (pass); } if (req.http.Accept-Encoding) { - if (req.url ~ "(?i)\.(jpg|jpeg|png|gif|webp|ico|svg|woff2?|ttf|eot|mp4|mp3|pdf|zip|7z|gz|bz2)$") { + if (req.url ~ "(?i)\.(jpg|jpeg|png|gif|webp|ico|svg|woff2?|ttf|eot|mp4|mp3|pdf|zip|7z|gz|bz2)(\?.*)?$") { unset req.http.Accept-Encoding; - } elseif (req.http.Accept-Encoding ~ "gzip") { + } elseif (req.http.Accept-Encoding ~ "(?i)gzip") { set req.http.Accept-Encoding = "gzip"; } else { unset req.http.Accept-Encoding; } } - # jeśli są cookies, to: - # - dla plików statycznych ignorujemy - # - dla reszty omijamy cache + # Cookies ignorujemy wyłącznie dla zasobów statycznych. if (req.http.Cookie) { - if (req.url ~ "(?i)\.(jpg|jpeg|png|gif|webp|ico|svg|js|css|woff2?|ttf|eot|pdf|zip|7z|gz|bz2|mp4|mp3)$") { + if (req.url ~ "(?i)\.(jpg|jpeg|png|gif|webp|ico|svg|js|css|woff2?|ttf|eot|pdf|zip|7z|gz|bz2|mp4|mp3)(\?.*)?$") { unset req.http.Cookie; } else { + set req.http.X-CS-Decision = "bypass"; return (pass); } } @@ -103,99 +120,134 @@ sub vcl_recv { } sub vcl_hash { + # Domyślny VCL dodaje req.url i Host. Dodajemy rozróżnienie HTTP/HTTPS. if (req.http.X-Forwarded-Proto) { hash_data(req.http.X-Forwarded-Proto); } } sub vcl_hit { - set req.http.X-Cache = "HIT"; - if (obj.ttl <= 0s && obj.grace > 0s) { - set req.http.X-Cache = "HIT-GRACE"; + if (obj.ttl >= 0s) { + set req.http.X-CS-Decision = "hit"; + return (deliver); } - return (deliver); + if (obj.ttl + obj.grace > 0s) { + set req.http.X-CS-Decision = "hit"; + return (deliver); + } + # Odswiezenie po wyczerpaniu grace to fwd=stale, nie uri-miss. + set req.http.X-CS-Decision = "stale"; + return (miss); } sub vcl_miss { - set req.http.X-Cache = "MISS"; + if (!req.http.X-CS-Decision) { + set req.http.X-CS-Decision = "miss"; + } return (fetch); } sub vcl_pass { - set req.http.X-Cache = "PASS"; + if (!req.http.X-CS-Decision) { + set req.http.X-CS-Decision = "bypass"; + } return (fetch); } +sub vcl_backend_fetch { + # Metadane pomocnicze nie trafiają do aplikacji. + unset bereq.http.X-CS-Decision; +} + sub vcl_backend_response { - # nie cache'uj błędów 5xx - if (beresp.status == 500 || beresp.status == 503) { + # Dla PASS nie zmieniaj polityki cache ani cookies aplikacji. + if (bereq.uncacheable) { set beresp.uncacheable = true; set beresp.ttl = 0s; return (deliver); } - # krótkie cache dla 404, umiarkowane dla redirectów + # Odpowiedzi 5xx i tresci jawnie prywatne nie moga trafic do cache. + if (beresp.status >= 500 && beresp.status <= 599) { + set beresp.uncacheable = true; + set beresp.ttl = 0s; + return (deliver); + } + + if (beresp.http.Cache-Control ~ "(?i)(no-store|private|no-cache)" || + beresp.http.Surrogate-Control ~ "(?i)no-store" || + beresp.http.Vary ~ "^[[:space:]]*\*[[:space:]]*$") { + set beresp.uncacheable = true; + set beresp.ttl = 0s; + return (deliver); + } + + # Odpowiedź personalizowana z Set-Cookie nie powinna być współdzielona. + if (bereq.url !~ "(?i)\.(jpg|jpeg|png|gif|webp|ico|svg|js|css|woff2?|ttf|eot|pdf|zip|7z|gz|bz2|mp4|mp3)(\?.*)?$" && + beresp.http.Set-Cookie) { + set beresp.uncacheable = true; + set beresp.ttl = 0s; + return (deliver); + } + + # Krótki cache dla 404, umiarkowany dla przekierowań. if (beresp.status == 404) { set beresp.ttl = 5s; } elseif (beresp.status == 301 || beresp.status == 302) { set beresp.ttl = 10m; } - if (bereq.url ~ "(?i)\.(jpg|jpeg|png|gif|webp|ico|svg|mp4|mp3|pdf|zip|7z|gz|bz2)$") { - set beresp.do_gzip = false; - } else { - set beresp.do_gzip = true; + # Wariant: statyki bez jawnej polityki pochodzenia dostają 7 dni. + if (beresp.status == 200 && + bereq.url ~ "(?i)\.(jpg|jpeg|png|gif|webp|ico|svg|js|css|woff2?|ttf|eot|pdf|zip|7z|gz|bz2|mp4|mp3)(\?.*)?$" && + !beresp.http.Cache-Control && !beresp.http.Expires) { + set beresp.ttl = 7d; + set beresp.http.Cache-Control = "public, max-age=604800, immutable"; } - if (beresp.http.Cache-Control ~ "(?i)no-store|private") { - set beresp.uncacheable = true; + # Gdy Varnish nie ma dodatniego TTL, odczytaj nagłówki cache. + if (beresp.ttl <= 0s) { + 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 + ); + } elseif (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 + ); + } + } + + if (beresp.ttl <= 0s) { set beresp.ttl = 0s; + set beresp.uncacheable = true; return (deliver); } - if (beresp.ttl <= 0s) { - if (beresp.http.Cache-Control ~ "(?i)s-maxage=\d+") { - set beresp.ttl = std.duration( - regsub(beresp.http.Cache-Control, ".*(?i)s-maxage=(\d+).*", "\1") + "s", - 0s - ); - } elseif (beresp.http.Cache-Control ~ "(?i)max-age=\d+") { - set beresp.ttl = std.duration( - regsub(beresp.http.Cache-Control, ".*(?i)max-age=(\d+).*", "\1") + "s", - 0s - ); - } - } - - if (beresp.ttl <= 0s) { - if (bereq.url ~ "(?i)\.(jpg|jpeg|png|gif|webp|ico|svg|js|css|woff2?|ttf|eot|pdf|zip|7z|gz|bz2|mp4|mp3)$") { - set beresp.ttl = 7d; - } else { - set beresp.ttl = 0s; - set beresp.uncacheable = true; - return (deliver); - } - } - - if (bereq.url ~ "(?i)\.(jpg|jpeg|png|gif|webp|ico|svg|js|css|woff2?|ttf|eot|pdf|zip|7z|gz|bz2|mp4|mp3)$") { + # Set-Cookie usuwamy tylko przy odpowiedzi 200 z publicznym plikiem. + if (beresp.status == 200 && + bereq.url ~ "(?i)\.(jpg|jpeg|png|gif|webp|ico|svg|js|css|woff2?|ttf|eot|pdf|zip|7z|gz|bz2|mp4|mp3)(\?.*)?$") { unset beresp.http.Set-Cookie; } + # Kompresuj tylko nieskompresowane typy tekstowe. + if (!beresp.http.Content-Encoding && + beresp.http.Content-Type ~ "(?i)^(text/|application/(javascript|json|xml))") { + set beresp.do_gzip = true; + } else { + set beresp.do_gzip = false; + } + if (beresp.http.Surrogate-Control ~ "ESI/1.0") { unset beresp.http.Surrogate-Control; set beresp.do_esi = true; } - if (beresp.ttl > 0s) { - set beresp.grace = 10m; - if (beresp.ttl > 1h) { - set beresp.keep = 1h; - } else { - set beresp.keep = beresp.ttl; - } + set beresp.grace = 10m; + if (beresp.ttl > 1h) { + set beresp.keep = 1h; } else { - set beresp.grace = 0s; - set beresp.keep = 0s; + set beresp.keep = beresp.ttl; } if (beresp.http.Content-Length && std.integer(beresp.http.Content-Length, 0) > 1048576) { @@ -206,29 +258,70 @@ sub vcl_backend_response { } sub vcl_deliver { - # Cache-Control tylko dla realnych plików - if (req.url ~ "(?i)\.(jpg|jpeg|png|gif|webp|ico|svg|js|css|woff2?|ttf|eot|pdf|zip|7z|gz|bz2|mp4|mp3)$") { - set resp.http.Cache-Control = "public, max-age=604800, immutable"; - } else { + # Polityka przeglądarki: nie nadpisuj private/no-store backendu na public. + if (req.url ~ "(?i)\.(jpg|jpeg|png|gif|webp|ico|svg|js|css|woff2?|ttf|eot|pdf|zip|7z|gz|bz2|mp4|mp3)(\?.*)?$" && + resp.status == 200 && !obj.uncacheable) { + if (!resp.http.Cache-Control) { + set resp.http.Cache-Control = "public, max-age=604800, immutable"; + } + } elseif (!resp.http.Cache-Control || + resp.http.Cache-Control !~ "(?i)(no-store|private)") { set resp.http.Cache-Control = "no-store, no-cache, must-revalidate, max-age=0"; } - unset resp.http.Expires; - unset resp.http.Pragma; - - if (resp.status >= 500) { - set resp.http.X-Cache = "ERROR"; - } elseif (obj.uncacheable) { - set resp.http.X-Cache = "PASS"; - unset resp.http.Age; - } elseif (obj.hits > 0) { - set resp.http.X-Cache = "HIT"; - set resp.http.X-Cache-Hits = obj.hits; + # Odpowiedz z vcl_backend_error to lokalny synthetic response. + # RFC 9211 zaleca, aby nie oznaczac jej jako MISS. + if (resp.http.X-CS-Local-Error) { + unset resp.http.Cache-Status; } else { - set resp.http.X-Cache = "MISS"; - unset resp.http.Age; + # RFC 9211: status dotyczy obsługi bieżącego żądania (nie obiektu w cache). + # Dodajemy własny wpis NA KOŃCU, zachowując wpisy wcześniejszych cache. + if (req.http.X-CS-Decision == "hit") { + # obj.ttl ma format np. 123.456; RFC wymaga całkowitych sekund. + # Wariant działa również ze starszym std (Varnish 6.0/6.1). + set resp.http.X-CS-TTL = obj.ttl; + set resp.http.X-CS-TTL = regsub(resp.http.X-CS-TTL, "[.][0-9]+$", ""); + set resp.http.X-CS-Entry = "varnish; hit; ttl=" + resp.http.X-CS-TTL; + } elseif (req.http.X-CS-Decision == "method") { + set resp.http.X-CS-Entry = "varnish; fwd=method"; + } elseif (req.http.X-CS-Decision == "request") { + set resp.http.X-CS-Entry = "varnish; fwd=request"; + } elseif (req.http.X-CS-Decision == "bypass") { + set resp.http.X-CS-Entry = "varnish; fwd=bypass"; + } elseif (req.http.X-CS-Decision == "miss" || req.http.X-CS-Decision == "stale") { + if (!obj.uncacheable && obj.ttl > 0s) { + set resp.http.X-CS-Entry = "varnish; fwd=" + + regsub(req.http.X-CS-Decision, "^miss$", "uri-miss") + "; stored"; + } else { + set resp.http.X-CS-Entry = "varnish; fwd=" + + regsub(req.http.X-CS-Decision, "^miss$", "uri-miss") + "; stored=?0"; + } + } else { + # Zabezpieczenie na nietypowe ścieżki VCL. + if (obj.uncacheable) { + set resp.http.X-CS-Entry = "varnish; fwd=bypass"; + } elseif (obj.hits > 0) { + set resp.http.X-CS-TTL = obj.ttl; + set resp.http.X-CS-TTL = regsub(resp.http.X-CS-TTL, "[.][0-9]+$", ""); + set resp.http.X-CS-Entry = "varnish; hit; ttl=" + resp.http.X-CS-TTL; + } else { + set resp.http.X-CS-Entry = "varnish; fwd=uri-miss"; + } + } + + if (resp.http.Cache-Status) { + set resp.http.Cache-Status = resp.http.Cache-Status + ", " + resp.http.X-CS-Entry; + } else { + set resp.http.Cache-Status = resp.http.X-CS-Entry; + } + } + unset resp.http.X-CS-Entry; + unset resp.http.X-CS-TTL; + unset resp.http.X-CS-Local-Error; + unset resp.http.X-Cache; + unset resp.http.X-Cache-Hits; unset resp.http.X-Url; unset resp.http.X-Host; unset resp.http.Via; @@ -239,12 +332,23 @@ sub vcl_deliver { set resp.http.X-Frame-Options = "SAMEORIGIN"; } +sub vcl_backend_error { + # Zachowaj standardowa tresc bledu z built-in VCL, bez cache'owania 503. + set beresp.ttl = 0s; + set beresp.http.X-CS-Local-Error = "1"; +} + sub vcl_synth { - set resp.http.X-Cache = "SYNTH"; + # Odpowiedzi syntetyczne (np. 429) nie raportują stanu cache. + unset resp.http.Cache-Status; + unset resp.http.X-Cache; + unset resp.http.X-Cache-Hits; unset resp.http.X-Varnish; + set resp.http.Cache-Control = "private, no-store"; if (resp.status == 429) { set resp.http.Content-Type = "text/plain; charset=utf-8"; return (deliver); } -} \ No newline at end of file + # Dla PURGE/BAN i innych SYNTH zachowaj tresc built-in VCL. +}