rfc9211
This commit is contained in:
1 parent
33c3ec0a0b
commit
95a63041dc
2 files changed
+111
-205
No files matched your search
@@ -1,7 +1,7 @@
|
||||
# --- Stage 1: build varnish + modules ---
|
||||
FROM debian:trixie-slim AS builder
|
||||
|
||||
ARG VARNISH_VERSION=8.0.0
|
||||
ARG VARNISH_VERSION=9.1.0
|
||||
ARG VARNISH_MODULES_VERSION=0.27.0
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
|
||||
+110
-204
@@ -1,33 +1,30 @@
|
||||
|
||||
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 {
|
||||
# Wewnętrzne znaczniki - nie ufamy wartościom od klienta
|
||||
unset req.http.X-Varnish-CS-Mode;
|
||||
unset req.http.X-Varnish-CS-Entry;
|
||||
# Internal markers can only be set by our own VCL.
|
||||
unset req.http.X-RFC9211-State;
|
||||
unset req.http.X-RFC9211-Entry;
|
||||
unset req.http.X-RFC9211-TTL;
|
||||
unset req.http.X-RFC9211-Synthetic;
|
||||
unset req.http.Cache-Status;
|
||||
|
||||
# Rate limit: 200 / 10s, blokada 60s
|
||||
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"));
|
||||
@@ -39,28 +36,25 @@ sub vcl_recv {
|
||||
set req.http.X-Forwarded-Proto = "https";
|
||||
}
|
||||
|
||||
# Healthcheck / internal
|
||||
if (req.url == "/healthcheck" || req.http.X-Internal-Check) {
|
||||
if (req.url ~ "^/healthcheck(\?.*)?$" || req.http.X-Internal-Check) {
|
||||
return (pass);
|
||||
}
|
||||
|
||||
# WebSocket / socket.io
|
||||
if (req.http.Upgrade ~ "(?i)websocket" ||
|
||||
req.url ~ "^/socket.io/") {
|
||||
# Only WebSocket upgrades use pipe, not Socket.IO polling.
|
||||
if (req.http.Upgrade ~ "(?i)websocket") {
|
||||
return (pipe);
|
||||
}
|
||||
if (req.url ~ "^/socket\.io/") {
|
||||
return (pass);
|
||||
}
|
||||
|
||||
# Metody inne niż GET/HEAD
|
||||
if (req.method != "GET" && req.method != "HEAD") {
|
||||
return (pass);
|
||||
}
|
||||
|
||||
# Authorization
|
||||
if (req.http.Authorization) {
|
||||
return (pass);
|
||||
}
|
||||
|
||||
# Accept-Encoding
|
||||
if (req.http.Accept-Encoding) {
|
||||
if (req.http.Accept-Encoding ~ "zstd") {
|
||||
set req.http.Accept-Encoding = "zstd";
|
||||
@@ -73,9 +67,9 @@ sub vcl_recv {
|
||||
}
|
||||
}
|
||||
|
||||
# Statyczne zasoby
|
||||
if (req.url ~ "^/static/" ||
|
||||
req.url ~ "\.(css|js|png|jpe?g|webp|svg|ico|woff2?)(\?.*)?$") {
|
||||
# Public static resources do not vary by session cookie.
|
||||
if (req.url ~ "^/(static|uploads)/" ||
|
||||
req.url ~ "(?i)\.(css|js|png|jpe?g|webp|svg|ico|woff2?)(\?.*)?$") {
|
||||
unset req.http.Cookie;
|
||||
return (hash);
|
||||
}
|
||||
@@ -83,156 +77,115 @@ sub vcl_recv {
|
||||
return (hash);
|
||||
}
|
||||
|
||||
# ===== PIPE =====
|
||||
sub vcl_pipe {
|
||||
if (req.http.Upgrade) {
|
||||
set bereq.http.Upgrade = req.http.Upgrade;
|
||||
set bereq.http.Connection = req.http.Connection;
|
||||
set bereq.http.Connection = "Upgrade";
|
||||
}
|
||||
}
|
||||
|
||||
# ===== HASH =====
|
||||
sub vcl_hash {
|
||||
hash_data(req.url);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return (lookup);
|
||||
}
|
||||
|
||||
# ===== CACHE STATUS: HIT =====
|
||||
# Track actual request transitions; leave the final return to builtin VCL.
|
||||
sub vcl_hit {
|
||||
# Zawartość świeża lub serwowana z grace
|
||||
if (obj.ttl >= 0s || obj.ttl + obj.grace > 0s) {
|
||||
set req.http.X-Varnish-CS-Mode = "hit";
|
||||
} else {
|
||||
# Obiekt wygasł i wymaga pobrania
|
||||
set req.http.X-Varnish-CS-Mode = "stale";
|
||||
}
|
||||
|
||||
# Dalsze decyzje pozostawiamy builtin VCL
|
||||
set req.http.X-RFC9211-State = "hit";
|
||||
}
|
||||
|
||||
# ===== CACHE STATUS: MISS =====
|
||||
sub vcl_miss {
|
||||
if (req.http.X-Varnish-CS-Mode != "stale") {
|
||||
set req.http.X-Varnish-CS-Mode = "miss";
|
||||
}
|
||||
# 'miss' is intentionally generic: we cannot always distinguish
|
||||
# uri-miss from vary-miss in this VCL hook.
|
||||
set req.http.X-RFC9211-State = "miss";
|
||||
}
|
||||
|
||||
# ===== CACHE STATUS: PASS =====
|
||||
sub vcl_pass {
|
||||
if (req.method != "GET" && req.method != "HEAD") {
|
||||
set req.http.X-Varnish-CS-Mode = "method";
|
||||
if (req.method == "GET" || req.method == "HEAD") {
|
||||
set req.http.X-RFC9211-State = "bypass";
|
||||
} else {
|
||||
set req.http.X-Varnish-CS-Mode = "bypass";
|
||||
set req.http.X-RFC9211-State = "method";
|
||||
}
|
||||
}
|
||||
|
||||
# ===== BACKEND FETCH =====
|
||||
sub vcl_backend_fetch {
|
||||
# Nie przekazujemy znaczników do aplikacji
|
||||
unset bereq.http.X-Varnish-CS-Mode;
|
||||
unset bereq.http.X-Varnish-CS-Entry;
|
||||
# Never forward diagnostic state to the application.
|
||||
unset bereq.http.X-RFC9211-State;
|
||||
unset bereq.http.X-RFC9211-Entry;
|
||||
unset bereq.http.X-RFC9211-TTL;
|
||||
unset bereq.http.X-RFC9211-Synthetic;
|
||||
}
|
||||
|
||||
# ===== BACKEND RESPONSE =====
|
||||
sub vcl_backend_response {
|
||||
# Zabezpieczenie wewnętrznych nagłówków
|
||||
unset beresp.http.X-Varnish-CS-Detail;
|
||||
unset beresp.http.X-Varnish-Local-Error;
|
||||
# Origin must not control our local backend-error marker.
|
||||
unset beresp.http.X-RFC9211-Synthetic;
|
||||
|
||||
# Respektuj no-store / private
|
||||
if (beresp.http.Cache-Control ~ "(?i)no-store|private") {
|
||||
# Never store backend 5xx responses.
|
||||
if (beresp.status >= 500) {
|
||||
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 == "*") {
|
||||
set beresp.uncacheable = true;
|
||||
set beresp.ttl = 0s;
|
||||
set beresp.http.X-Varnish-CS-Detail = "uncacheable";
|
||||
return (deliver);
|
||||
}
|
||||
|
||||
# Redirecty bez cache
|
||||
if (beresp.status >= 300 && beresp.status < 400) {
|
||||
set beresp.uncacheable = true;
|
||||
set beresp.ttl = 0s;
|
||||
set beresp.http.X-Varnish-CS-Detail = "redirect";
|
||||
return (deliver);
|
||||
}
|
||||
|
||||
# Statyczne: cache tylko HTTP 200
|
||||
if (bereq.url ~ "^/static/" ||
|
||||
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;
|
||||
set beresp.http.X-Varnish-CS-Detail = "invalid-static-status";
|
||||
return (deliver);
|
||||
}
|
||||
}
|
||||
|
||||
# HTML zamiast JS/CSS - nie cache'uj
|
||||
if (bereq.url ~ "\.(js|css)(\?.*)?$" &&
|
||||
if (bereq.url ~ "(?i)\.(js|css)(\?.*)?$" &&
|
||||
beresp.http.Content-Type ~ "(?i)text/html") {
|
||||
set beresp.uncacheable = true;
|
||||
set beresp.ttl = 0s;
|
||||
set beresp.http.X-Varnish-CS-Detail = "html-instead-of-static";
|
||||
return (deliver);
|
||||
}
|
||||
|
||||
# Content-Type przy brakującym nagłówku
|
||||
if (bereq.url ~ "\.js(\?.*)?$" &&
|
||||
!beresp.http.Content-Type) {
|
||||
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 ~ "(?i)\.css(\?.*)?$" && !beresp.http.Content-Type) {
|
||||
set beresp.http.Content-Type = "text/css; charset=utf-8";
|
||||
}
|
||||
|
||||
if (bereq.url ~ "\.css(\?.*)?$" &&
|
||||
!beresp.http.Content-Type) {
|
||||
set beresp.http.Content-Type =
|
||||
"text/css; charset=utf-8";
|
||||
}
|
||||
|
||||
# ===== STATYCZNE =====
|
||||
if (bereq.url ~ "^/static/" ||
|
||||
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;
|
||||
|
||||
# Usuń Vary: Cookie
|
||||
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:]]*$", ""
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -240,41 +193,24 @@ sub vcl_backend_response {
|
||||
|
||||
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.http.Cache-Control = "public, max-age=86400, immutable";
|
||||
}
|
||||
|
||||
set beresp.grace = 1h;
|
||||
set beresp.keep = 24h;
|
||||
}
|
||||
|
||||
# ===== TTL =====
|
||||
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
|
||||
);
|
||||
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
|
||||
);
|
||||
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;
|
||||
}
|
||||
} else if (beresp.ttl <= 0s) {
|
||||
set beresp.ttl = 60s;
|
||||
}
|
||||
|
||||
if (beresp.http.Cache-Control ~ "(?i)immutable") {
|
||||
@@ -282,117 +218,87 @@ sub vcl_backend_response {
|
||||
set beresp.keep = 24h;
|
||||
}
|
||||
|
||||
# ===== GZIP =====
|
||||
if (!beresp.http.Content-Encoding &&
|
||||
bereq.http.Accept-Encoding ~ "gzip") {
|
||||
|
||||
if (beresp.http.Content-Type ~
|
||||
"(?i)text/|application/(javascript|json|xml)") {
|
||||
if (!beresp.http.Content-Encoding && bereq.http.Accept-Encoding ~ "gzip") {
|
||||
if (beresp.http.Content-Type ~ "(?i)text/|application/(javascript|json|xml)") {
|
||||
set beresp.do_gzip = true;
|
||||
}
|
||||
}
|
||||
|
||||
# ===== STREAMING =====
|
||||
if (beresp.http.Content-Length &&
|
||||
std.integer(beresp.http.Content-Length, 0) > 1048576) {
|
||||
set beresp.do_stream = true;
|
||||
}
|
||||
}
|
||||
|
||||
# ===== BACKEND ERROR =====
|
||||
sub vcl_backend_error {
|
||||
# Odpowiedź wygenerowana lokalnie - bez Cache-Status
|
||||
set beresp.http.X-Varnish-Local-Error = "1";
|
||||
# Synthetic fetch errors were not served from cache.
|
||||
set beresp.uncacheable = true;
|
||||
set beresp.ttl = 0s;
|
||||
set beresp.http.X-RFC9211-Synthetic = "1";
|
||||
return (deliver);
|
||||
}
|
||||
|
||||
# ===== DELIVER: RFC 9211 =====
|
||||
sub vcl_deliver {
|
||||
unset req.http.X-Varnish-CS-Entry;
|
||||
|
||||
# Nie oznaczamy lokalnych błędów jako odpowiedzi backendu
|
||||
if (!resp.http.X-Varnish-Local-Error) {
|
||||
|
||||
# HIT - również stale/grace dostarczony z cache
|
||||
if (req.http.X-Varnish-CS-Mode == "hit") {
|
||||
set req.http.X-Varnish-CS-Entry = "varnish; hit";
|
||||
|
||||
# TTL jako integer, również ujemny
|
||||
set req.http.X-Varnish-CS-Entry =
|
||||
req.http.X-Varnish-CS-Entry +
|
||||
"; ttl=" +
|
||||
std.real2integer(std.real(obj.ttl, 0.0), 0);
|
||||
|
||||
# MISS / STALE - pobranie z backendu
|
||||
} else if (req.http.X-Varnish-CS-Mode == "miss" ||
|
||||
req.http.X-Varnish-CS-Mode == "stale") {
|
||||
|
||||
set req.http.X-Varnish-CS-Entry =
|
||||
"varnish; fwd=" + req.http.X-Varnish-CS-Mode;
|
||||
|
||||
if (!obj.uncacheable && obj.ttl > 0s) {
|
||||
set req.http.X-Varnish-CS-Entry =
|
||||
req.http.X-Varnish-CS-Entry + "; stored";
|
||||
|
||||
set req.http.X-Varnish-CS-Entry =
|
||||
req.http.X-Varnish-CS-Entry +
|
||||
"; ttl=" +
|
||||
std.real2integer(std.real(obj.ttl, 0.0), 0);
|
||||
}
|
||||
|
||||
# PASS / metoda wymagająca forwardowania
|
||||
} else if (req.http.X-Varnish-CS-Mode == "bypass" ||
|
||||
req.http.X-Varnish-CS-Mode == "method") {
|
||||
|
||||
set req.http.X-Varnish-CS-Entry =
|
||||
"varnish; fwd=" + req.http.X-Varnish-CS-Mode;
|
||||
}
|
||||
|
||||
# Opcjonalny detail z backend_response
|
||||
if (req.http.X-Varnish-CS-Entry &&
|
||||
resp.http.X-Varnish-CS-Detail) {
|
||||
set req.http.X-Varnish-CS-Entry =
|
||||
req.http.X-Varnish-CS-Entry +
|
||||
"; detail=" + resp.http.X-Varnish-CS-Detail;
|
||||
}
|
||||
|
||||
# RFC 9211: zachowaj wpisy poprzednich cache
|
||||
if (req.http.X-Varnish-CS-Entry) {
|
||||
if (resp.http.Cache-Status) {
|
||||
set resp.http.Cache-Status =
|
||||
resp.http.Cache-Status + ", " +
|
||||
req.http.X-Varnish-CS-Entry;
|
||||
# Cache-Status MUST be calculated per request, not cached as an origin header.
|
||||
# Synthetic backend errors do not receive a local Cache-Status member.
|
||||
if (resp.http.X-RFC9211-Synthetic) {
|
||||
unset resp.http.Cache-Status;
|
||||
} else {
|
||||
if (req.http.X-RFC9211-State == "hit") {
|
||||
# VCL DURATION -> STRING produces seconds with 3 decimal places.
|
||||
# RFC 9211 ttl is an integer; negative subsecond TTL is rounded to -1.
|
||||
set req.http.X-RFC9211-TTL = obj.ttl;
|
||||
if (obj.ttl < 0s && obj.ttl > -1s) {
|
||||
set req.http.X-RFC9211-TTL = "-1";
|
||||
} else {
|
||||
set resp.http.Cache-Status =
|
||||
req.http.X-Varnish-CS-Entry;
|
||||
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; stored=?0";
|
||||
} else if (req.http.X-RFC9211-State == "bypass") {
|
||||
set req.http.X-RFC9211-Entry = "varnish; fwd=bypass; stored=?0";
|
||||
}
|
||||
|
||||
# Preserve the existing upstream cache chain, append Varnish LAST.
|
||||
# If the request path is unknown, avoid fabricating a bypass.
|
||||
if (req.http.X-RFC9211-Entry) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Czyszczenie wewnętrznych nagłówków
|
||||
unset resp.http.X-Varnish-CS-Detail;
|
||||
unset resp.http.X-Varnish-Local-Error;
|
||||
unset resp.http.X-Pass-Reason;
|
||||
unset resp.http.X-RFC9211-Synthetic;
|
||||
unset resp.http.X-Cache;
|
||||
|
||||
# Age zachowujemy dla odpowiedzi z cache
|
||||
if (obj.uncacheable || obj.hits == 0) {
|
||||
unset resp.http.Age;
|
||||
}
|
||||
|
||||
unset resp.http.X-Pass-Reason;
|
||||
unset resp.http.Via;
|
||||
unset resp.http.X-Varnish;
|
||||
unset resp.http.Server;
|
||||
unset resp.http.Content-Disposition;
|
||||
# Preserve Age (it is standard HTTP cache metadata).
|
||||
}
|
||||
|
||||
# ===== SYNTH =====
|
||||
sub vcl_synth {
|
||||
# RFC 9211: bez Cache-Status dla lokalnych odpowiedzi
|
||||
set resp.http.Cache-Control = "private, no-store, no-cache";
|
||||
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 =====
|
||||
sub vcl_purge {
|
||||
return (synth(200, "Purged"));
|
||||
}
|
||||
Reference in new issue
Block a user