Files
zbiorki_app/deploy/varnish/default.vcl.template
T
2026-10-26 16:43:34 +01:00

305 lines
8.6 KiB
Plaintext

vcl 4.1;
import vsthrottle;
import std;
backend app {
.host = "app";
.port = "${APP_PORT}";
}
acl purge {
"127.0.0.1";
"::1";
}
sub vcl_recv {
# 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;
if (vsthrottle.is_denied(client.identity, 200, 10s, 60s)) {
return (synth(429, "Too Many Requests"));
}
if (req.method == "PURGE") {
if (!client.ip ~ purge) {
return (synth(405, "Not allowed"));
}
return (purge);
}
if (!req.http.X-Forwarded-Proto) {
set req.http.X-Forwarded-Proto = "https";
}
if (req.url ~ "^/healthcheck(\?.*)?$" || req.http.X-Internal-Check) {
return (pass);
}
# Only WebSocket upgrades use pipe, not Socket.IO polling.
if (req.http.Upgrade ~ "(?i)websocket") {
return (pipe);
}
if (req.url ~ "^/socket\.io/") {
return (pass);
}
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
}
if (req.http.Authorization) {
return (pass);
}
if (req.http.Accept-Encoding) {
if (req.http.Accept-Encoding ~ "zstd") {
set req.http.Accept-Encoding = "zstd";
} else if (req.http.Accept-Encoding ~ "br") {
set req.http.Accept-Encoding = "br";
} else if (req.http.Accept-Encoding ~ "gzip") {
set req.http.Accept-Encoding = "gzip";
} else {
set req.http.Accept-Encoding = "identity";
}
}
# 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);
}
return (hash);
}
sub vcl_pipe {
if (req.http.Upgrade) {
set bereq.http.Upgrade = req.http.Upgrade;
set bereq.http.Connection = "Upgrade";
}
}
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);
}
}
# Track actual request transitions; leave the final return to builtin VCL.
sub vcl_hit {
set req.http.X-RFC9211-State = "hit";
}
sub vcl_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";
}
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 {
# 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;
}
sub vcl_backend_response {
# Origin must not control our local backend-error marker.
unset beresp.http.X-RFC9211-Synthetic;
# 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;
return (deliver);
}
if (beresp.status >= 300 && beresp.status < 400) {
set beresp.uncacheable = true;
set beresp.ttl = 0s;
return (deliver);
}
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;
return (deliver);
}
}
if (bereq.url ~ "(?i)\.(js|css)(\?.*)?$" &&
beresp.http.Content-Type ~ "(?i)text/html") {
set beresp.uncacheable = true;
set beresp.ttl = 0s;
return (deliver);
}
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 ~ "^/(static|uploads)/" ||
bereq.url ~ "(?i)\.(css|js|png|jpe?g|webp|svg|ico|woff2?)(\?.*)?$") {
unset beresp.http.Set-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:]]*$", "");
if (beresp.http.Vary ~ "^[[:space:]]*$") {
unset beresp.http.Vary;
}
}
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;
}
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.http.Cache-Control ~ "(?i)immutable") {
set beresp.grace = 1h;
set beresp.keep = 24h;
}
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;
}
}
if (beresp.http.Content-Length &&
std.integer(beresp.http.Content-Length, 0) > 1048576) {
set beresp.do_stream = true;
}
}
sub vcl_backend_error {
# 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);
}
sub vcl_deliver {
# 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 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;
}
}
}
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;
# Preserve Age (it is standard HTTP cache metadata).
}
sub vcl_synth {
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;
}
sub vcl_purge {
return (synth(200, "Purged"));
}