vrnish rmf9211
This commit is contained in:
1 parent
b31964db81
commit
df5ca24eca
1 file changed
+138
-124
+138
-124
@@ -3,52 +3,57 @@ vcl 4.1;
|
||||
import vsthrottle;
|
||||
import std;
|
||||
|
||||
# ===== Backend =====
|
||||
backend app {
|
||||
.host = "app";
|
||||
.port = "${APP_PORT}";
|
||||
}
|
||||
|
||||
# ===== ACL =====
|
||||
acl purge {
|
||||
"127.0.0.1";
|
||||
"::1";
|
||||
}
|
||||
|
||||
# ===== RECV =====
|
||||
sub vcl_recv {
|
||||
# RATE LIMIT: 200 żądań / 10s, blokada 60s
|
||||
# Do not trust client-supplied internal state headers.
|
||||
unset req.http.X-RFC9211-State;
|
||||
unset req.http.X-RFC9211-Entry;
|
||||
unset req.http.X-RFC9211-TTL;
|
||||
|
||||
if (vsthrottle.is_denied(client.identity, 200, 10s, 60s)) {
|
||||
return (synth(429, "Too Many Requests"));
|
||||
}
|
||||
|
||||
# PURGE tylko lokalnie
|
||||
if (req.method == "PURGE") {
|
||||
if (!client.ip ~ purge) { return (synth(405, "Not allowed")); }
|
||||
if (!client.ip ~ purge) {
|
||||
return (synth(405, "Not allowed"));
|
||||
}
|
||||
return (purge);
|
||||
}
|
||||
|
||||
# omijamy cache dla healthchecków / wewnętrznych nagłówków
|
||||
if (req.url == "/healthcheck" || req.http.X-Internal-Check) { return (pass); }
|
||||
|
||||
# WebSocket wymaga surowego tunelu TCP, ale zwykły transport polling
|
||||
# Socket.IO jest normalnym HTTP i nie może trafiać do pipe. Puszczenie całego
|
||||
# /socket.io/ przez pipe może pozostawić połączenie HTTP przypięte do backendu
|
||||
# po uśpieniu mobilnej przeglądarki i utrudniać późniejszy refresh/nawigację.
|
||||
if (req.http.Upgrade ~ "(?i)websocket") {
|
||||
return (pipe);
|
||||
if (!req.http.X-Forwarded-Proto) {
|
||||
set req.http.X-Forwarded-Proto = "https";
|
||||
}
|
||||
if (req.url ~ "^/socket.io/") {
|
||||
|
||||
if (req.url ~ "^/healthcheck(\?.*)?$" || req.http.X-Internal-Check) {
|
||||
return (pass);
|
||||
}
|
||||
|
||||
# metody inne niż GET/HEAD bez cache
|
||||
if (req.method != "GET" && req.method != "HEAD") { return (pass); }
|
||||
# Only WebSocket upgrades use pipe, never Socket.IO polling.
|
||||
if (req.http.Upgrade ~ "(?i)websocket") {
|
||||
return (pipe);
|
||||
}
|
||||
if (req.url ~ "^/socket\.io/") {
|
||||
return (pass);
|
||||
}
|
||||
|
||||
# Żądania z Authorization nie są buforowane
|
||||
if (req.http.Authorization) { return (pass); }
|
||||
if (req.method != "GET" && req.method != "HEAD") {
|
||||
return (pass);
|
||||
}
|
||||
if (req.http.Authorization) {
|
||||
return (pass);
|
||||
}
|
||||
|
||||
# ---- Normalizacja Accept-Encoding (kolejność: zstd > br > gzip) ----
|
||||
# Preserve the original encoding normalization policy.
|
||||
if (req.http.Accept-Encoding) {
|
||||
if (req.http.Accept-Encoding ~ "zstd") {
|
||||
set req.http.Accept-Encoding = "zstd";
|
||||
@@ -61,51 +66,16 @@ 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 ~ "^/uploads/" || req.url ~ "\.(css|js|png|jpe?g|webp|svg|ico|woff2?)$") {
|
||||
# Cache public static files independently of session cookies.
|
||||
if (req.url ~ "^/(static|uploads)/" ||
|
||||
req.url ~ "(?i)\.(css|js|png|jpe?g|webp|svg|ico|woff2?)(\?.*)?$") {
|
||||
unset req.http.Cookie;
|
||||
unset req.http.Authorization;
|
||||
return (hash);
|
||||
}
|
||||
|
||||
if (!req.http.X-Forwarded-Proto) {
|
||||
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);
|
||||
}
|
||||
|
||||
# ===== PIPE (WebSocket passthrough) =====
|
||||
sub vcl_pipe {
|
||||
if (req.http.Upgrade) {
|
||||
set bereq.http.Upgrade = req.http.Upgrade;
|
||||
@@ -113,41 +83,68 @@ sub vcl_pipe {
|
||||
}
|
||||
}
|
||||
|
||||
# ===== 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); }
|
||||
if (req.http.host) {
|
||||
hash_data(req.http.host);
|
||||
} else {
|
||||
hash_data(server.ip);
|
||||
}
|
||||
if (req.http.Cookie) {
|
||||
hash_data(req.http.Cookie);
|
||||
}
|
||||
if (req.http.Accept-Encoding) {
|
||||
hash_data(req.http.Accept-Encoding);
|
||||
}
|
||||
if (req.http.X-Accept-Image) {
|
||||
hash_data(req.http.X-Accept-Image);
|
||||
}
|
||||
}
|
||||
|
||||
# These hooks remember how the CURRENT request was handled.
|
||||
sub vcl_hit {
|
||||
set req.http.X-RFC9211-State = "hit";
|
||||
}
|
||||
|
||||
sub vcl_miss {
|
||||
set req.http.X-RFC9211-State = "miss";
|
||||
}
|
||||
|
||||
sub vcl_pass {
|
||||
if (req.method == "GET" || req.method == "HEAD") {
|
||||
set req.http.X-RFC9211-State = "bypass";
|
||||
} else {
|
||||
set req.http.X-RFC9211-State = "method";
|
||||
}
|
||||
}
|
||||
|
||||
sub vcl_backend_fetch {
|
||||
# Internal markers must never reach the origin application.
|
||||
unset bereq.http.X-RFC9211-State;
|
||||
unset bereq.http.X-RFC9211-Entry;
|
||||
unset bereq.http.X-RFC9211-TTL;
|
||||
}
|
||||
|
||||
# ===== BACKEND_RESPONSE =====
|
||||
sub vcl_backend_response {
|
||||
# Zakaz cache – respektujemy
|
||||
if (beresp.http.Cache-Control ~ "(?i)no-store|private") {
|
||||
# The synthetic marker belongs only to Varnish, not to the origin.
|
||||
unset beresp.http.X-RFC9211-Synthetic;
|
||||
|
||||
if (beresp.http.Cache-Control ~ "(?i)no-store|private|no-cache" ||
|
||||
beresp.http.Surrogate-Control ~ "(?i)no-store" ||
|
||||
beresp.http.Vary == "*") {
|
||||
set beresp.uncacheable = true;
|
||||
set beresp.ttl = 0s;
|
||||
set beresp.http.X-Pass-Reason = "no-store";
|
||||
return (deliver);
|
||||
}
|
||||
|
||||
# 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;
|
||||
set beresp.http.X-Pass-Reason = "redirect";
|
||||
return (deliver);
|
||||
}
|
||||
|
||||
# Nie cache'uj statyków, jeśli status ≠ 200
|
||||
if (bereq.url ~ "^/static/" || bereq.url ~ "^/uploads/" || bereq.url ~ "\.(css|js|png|jpe?g|webp|svg|ico|woff2?)($|\?)") {
|
||||
if (bereq.url ~ "^/(static|uploads)/" ||
|
||||
bereq.url ~ "(?i)\.(css|js|png|jpe?g|webp|svg|ico|woff2?)(\?.*)?$") {
|
||||
if (beresp.status != 200) {
|
||||
set beresp.uncacheable = true;
|
||||
set beresp.ttl = 0s;
|
||||
@@ -155,121 +152,138 @@ sub vcl_backend_response {
|
||||
}
|
||||
}
|
||||
|
||||
# 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") {
|
||||
# An HTML login page returned as a JS/CSS asset must not be cached.
|
||||
if (bereq.url ~ "(?i)\.(js|css)(\?.*)?$" &&
|
||||
beresp.http.Content-Type ~ "(?i)text/html") {
|
||||
set beresp.uncacheable = true;
|
||||
set beresp.ttl = 0s;
|
||||
return (deliver);
|
||||
}
|
||||
|
||||
# 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 ~ "(?i)\.js(\?.*)?$" && !beresp.http.Content-Type) {
|
||||
set beresp.http.Content-Type = "application/javascript; charset=utf-8";
|
||||
}
|
||||
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";
|
||||
}
|
||||
if (bereq.url ~ "(?i)\.css(\?.*)?$" && !beresp.http.Content-Type) {
|
||||
set beresp.http.Content-Type = "text/css; charset=utf-8";
|
||||
}
|
||||
|
||||
# ---- STATYCZNE: zdejmij Set-Cookie i Vary: Cookie, zapewnij TTL ----
|
||||
if (bereq.url ~ "^/static/" || bereq.url ~ "^/uploads/" || bereq.url ~ "\.(css|js|png|jpe?g|webp|svg|ico|woff2?)$") {
|
||||
if (bereq.url ~ "^/(static|uploads)/" ||
|
||||
bereq.url ~ "(?i)\.(css|js|png|jpe?g|webp|svg|ico|woff2?)(\?.*)?$") {
|
||||
unset beresp.http.Set-Cookie;
|
||||
|
||||
# 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, "(?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;
|
||||
}
|
||||
}
|
||||
|
||||
# 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";
|
||||
}
|
||||
|
||||
set beresp.grace = 1h;
|
||||
set beresp.keep = 24h;
|
||||
set beresp.keep = 24h;
|
||||
}
|
||||
|
||||
# ---- 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; }
|
||||
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;
|
||||
set beresp.keep = 24h;
|
||||
}
|
||||
|
||||
# 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;
|
||||
}
|
||||
}
|
||||
|
||||
# Duże odpowiedzi streamujemy
|
||||
if (beresp.http.Content-Length && std.integer(beresp.http.Content-Length, 0) > 1048576) {
|
||||
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 {
|
||||
# Do not claim a synthetic backend-fetch error was served by a cache.
|
||||
set beresp.uncacheable = true;
|
||||
set beresp.ttl = 0s;
|
||||
set beresp.http.X-RFC9211-Synthetic = "1";
|
||||
return (deliver);
|
||||
}
|
||||
|
||||
# ===== DELIVER =====
|
||||
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) { # z backendu
|
||||
set resp.http.X-Cache = "PASS:" + resp.http.X-Pass-Reason;
|
||||
} else {
|
||||
set resp.http.X-Cache = "PASS";
|
||||
}
|
||||
unset resp.http.X-Pass-Reason;
|
||||
unset resp.http.Age;
|
||||
} else if (obj.hits > 0) {
|
||||
set resp.http.X-Cache = "HIT";
|
||||
if (resp.http.X-RFC9211-Synthetic) {
|
||||
unset resp.http.Cache-Status;
|
||||
} else {
|
||||
set resp.http.X-Cache = "MISS";
|
||||
unset resp.http.Age;
|
||||
if (req.http.X-RFC9211-State == "hit") {
|
||||
# DURATION -> STRING works with VCL 4.1 / Varnish 6.0+.
|
||||
# Structured Fields require an integer, not a decimal string.
|
||||
set req.http.X-RFC9211-TTL = obj.ttl;
|
||||
if (obj.ttl < 0s && obj.ttl > -1s) {
|
||||
set req.http.X-RFC9211-TTL = "-1";
|
||||
} else {
|
||||
set req.http.X-RFC9211-TTL = regsub(req.http.X-RFC9211-TTL, "\.[0-9]+$", "");
|
||||
}
|
||||
set req.http.X-RFC9211-Entry = "varnish; hit; ttl=" + req.http.X-RFC9211-TTL;
|
||||
} else if (req.http.X-RFC9211-State == "miss") {
|
||||
set req.http.X-RFC9211-Entry = "varnish; fwd=miss";
|
||||
if (obj.uncacheable) {
|
||||
set req.http.X-RFC9211-Entry = req.http.X-RFC9211-Entry + "; stored=?0";
|
||||
} else {
|
||||
set req.http.X-RFC9211-Entry = req.http.X-RFC9211-Entry + "; stored";
|
||||
}
|
||||
} else if (req.http.X-RFC9211-State == "method") {
|
||||
set req.http.X-RFC9211-Entry = "varnish; fwd=method";
|
||||
} else {
|
||||
set req.http.X-RFC9211-Entry = "varnish; fwd=bypass";
|
||||
}
|
||||
|
||||
# RFC 9211: preserve the cache chain and append our own member LAST.
|
||||
if (resp.http.Cache-Status) {
|
||||
set resp.http.Cache-Status = resp.http.Cache-Status + ", " + req.http.X-RFC9211-Entry;
|
||||
} else {
|
||||
set resp.http.Cache-Status = req.http.X-RFC9211-Entry;
|
||||
}
|
||||
}
|
||||
|
||||
unset resp.http.X-RFC9211-Synthetic;
|
||||
unset resp.http.X-Cache;
|
||||
unset resp.http.X-Pass-Reason;
|
||||
unset resp.http.Via;
|
||||
unset resp.http.X-Varnish;
|
||||
unset resp.http.Server;
|
||||
unset resp.http.Content-Disposition;
|
||||
# Keep Age: it is useful cache metadata and can reflect upstream age.
|
||||
}
|
||||
|
||||
sub vcl_synth {
|
||||
set resp.http.Cache-Control = "private, no-store, no-cache";
|
||||
set resp.http.X-Cache = "SYNTH";
|
||||
unset resp.http.Cache-Status;
|
||||
unset resp.http.X-Cache;
|
||||
unset resp.http.X-Pass-Reason;
|
||||
unset resp.http.Via;
|
||||
unset resp.http.X-Varnish;
|
||||
unset resp.http.Server;
|
||||
unset resp.http.Content-Disposition;
|
||||
}
|
||||
|
||||
# ===== PURGE HANDLER =====
|
||||
sub vcl_purge {
|
||||
return (synth(200, "Purged"));
|
||||
}
|
||||
Reference in new issue
Block a user