216 lines
7.0 KiB
Plaintext
216 lines
7.0 KiB
Plaintext
vcl 4.1;
|
||
|
||
import std;
|
||
import vsthrottle;
|
||
|
||
backend default {
|
||
.host = "plik";
|
||
.port = "8080";
|
||
.max_connections = 100;
|
||
.probe = { .url = "/"; .interval = 10s; .timeout = 5s; .window = 5; .threshold = 3; }
|
||
.connect_timeout = 5s;
|
||
.first_byte_timeout = 90s;
|
||
.between_bytes_timeout = 2s;
|
||
}
|
||
|
||
acl purge { "localhost"; "127.0.0.1"; "::1"; }
|
||
|
||
sub vcl_recv {
|
||
# --- anty-noise / normalizacja ---
|
||
unset req.http.X-Cache;
|
||
unset req.http.X-Cache-Hits;
|
||
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, "\?$", "");
|
||
set req.http.Surrogate-Capability = "key=ESI/1.0";
|
||
|
||
if (req.restarts == 0) {
|
||
if (req.http.X-Forwarded-For) { set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip; }
|
||
else { set req.http.X-Forwarded-For = client.ip; }
|
||
}
|
||
|
||
# --- RATE LIMIT: 100/10s, kara 10s ---
|
||
if (vsthrottle.is_denied(client.identity, 100, 10s, 10s)) {
|
||
return (synth(429, "Too Many Requests"));
|
||
}
|
||
|
||
# --- metody administracyjne ---
|
||
if (req.method == "PURGE") {
|
||
if (!client.ip ~ purge) { return (synth(405, "Not allowed.")); }
|
||
return (hash);
|
||
}
|
||
if (req.method == "BAN") {
|
||
if (!client.ip ~ purge) { return (synth(405, "Not allowed.")); }
|
||
ban("req.http.host == " + req.http.host + " && req.url ~ " + req.url);
|
||
return (synth(200, "Banned"));
|
||
}
|
||
|
||
# --- dopuszczalne metody / pass dla niecache’owalnych ---
|
||
if (req.method != "GET" && req.method != "HEAD" && req.method != "OPTIONS") {
|
||
return (pass);
|
||
}
|
||
if (req.http.Authorization) { return (pass); }
|
||
|
||
# --- wyjątki dynamiczne (np. admin, ajax, status) ---
|
||
if (req.url ~ "(?i)/(ajax|ahah)/") {
|
||
return (pass);
|
||
}
|
||
|
||
# --- Accept-Encoding (nie kompresujemy oczywistych statyk po rozszerzeniu) ---
|
||
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)$") {
|
||
unset req.http.Accept-Encoding;
|
||
} elseif (req.http.Accept-Encoding ~ "gzip") {
|
||
set req.http.Accept-Encoding = "gzip";
|
||
} elseif (req.http.Accept-Encoding ~ "deflate" && req.http.user-agent !~ "MSIE") {
|
||
set req.http.Accept-Encoding = "deflate";
|
||
} else {
|
||
unset req.http.Accept-Encoding;
|
||
}
|
||
}
|
||
|
||
# --- cookies: tylko jeśli naprawdę potrzebne do cache key; inaczej wyczyść ---
|
||
if (req.http.Cookie) {
|
||
set req.http.Cookie = ";" + req.http.Cookie;
|
||
set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
|
||
# przykładowy whitelist (dopasuj pod aplikację); tu czyścimy wszystkie
|
||
set req.http.Cookie = regsuball(req.http.Cookie, ";[^;]*", "");
|
||
set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
|
||
if (req.http.Cookie ~ "^\s*$") { unset req.http.Cookie; }
|
||
else { return (pass); } # zostało coś istotnego → omijamy cache
|
||
}
|
||
|
||
return (hash);
|
||
}
|
||
|
||
sub vcl_hash {
|
||
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"; }
|
||
}
|
||
sub vcl_miss { set req.http.X-Cache = "MISS"; }
|
||
sub vcl_pass { set req.http.X-Cache = "PASS"; }
|
||
|
||
sub vcl_backend_response {
|
||
# krótkie TTL dla wybranych statusów
|
||
if (beresp.status == 404 || beresp.status == 301 || beresp.status == 500) {
|
||
set beresp.ttl = 10m;
|
||
}
|
||
|
||
# retry na 5xx (bez pętli)
|
||
if (beresp.status == 500 || beresp.status == 503) { return (retry); }
|
||
|
||
# kompresja
|
||
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;
|
||
}
|
||
|
||
# TTL: honoruj Cache-Control; no-store/private = 0
|
||
if (beresp.http.Cache-Control ~ "(?i)no-store|private") {
|
||
set beresp.ttl = 0s;
|
||
} else {
|
||
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);
|
||
}
|
||
# fallback (lekko agresywny, prosto)
|
||
if (beresp.ttl <= 0s) {
|
||
if (beresp.http.Content-Type ~ "(?i)^image/|^font/|/javascript|/css") { set beresp.ttl = 7d; }
|
||
elseif (beresp.http.Content-Type ~ "(?i)^text/|^application/json") { set beresp.ttl = 1d; }
|
||
else { set beresp.ttl = 1h; }
|
||
}
|
||
}
|
||
|
||
# usuń ciasteczka dla statyk/binariów
|
||
if (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;
|
||
}
|
||
|
||
# ESI
|
||
if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
|
||
unset beresp.http.Surrogate-Control;
|
||
set beresp.do_esi = true;
|
||
}
|
||
|
||
# grace/keep
|
||
if (beresp.ttl > 0s) {
|
||
set beresp.grace = std.duration(std.tostring( beresp.ttl / 10 ), 10m);
|
||
if (beresp.grace < 10m) { set beresp.grace = 10m; }
|
||
if (beresp.grace > 2h) { set beresp.grace = 2h; }
|
||
if (beresp.ttl > 1h) {
|
||
set beresp.keep = 1h;
|
||
} else {
|
||
set beresp.keep = beresp.ttl;
|
||
}
|
||
} else {
|
||
set beresp.grace = 0s;
|
||
set beresp.keep = 0s;
|
||
}
|
||
|
||
# streaming dużych odpowiedzi (>1 MiB)
|
||
if (beresp.http.Content-Length && std.integer(beresp.http.Content-Length, 0) > 1048576) {
|
||
set beresp.do_stream = true;
|
||
}
|
||
}
|
||
|
||
sub vcl_deliver {
|
||
# polityka Cache-Control po typie (frontend)
|
||
if (resp.http.Content-Type ~ "(?i)^image/|^font/|/javascript|/css") {
|
||
set resp.http.Cache-Control = "public, max-age=604800"; # 7d
|
||
} elseif (resp.http.Content-Type ~ "(?i)^text/|^application/json") {
|
||
set resp.http.Cache-Control = "public, max-age=86400"; # 1d
|
||
}
|
||
|
||
unset resp.http.Expires;
|
||
unset resp.http.Pragma;
|
||
|
||
# metryki cache
|
||
if (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;
|
||
} else {
|
||
set resp.http.X-Cache = "MISS";
|
||
unset resp.http.Age;
|
||
}
|
||
|
||
# twarde usunięcie sygnatur serwera
|
||
unset resp.http.X-Url;
|
||
unset resp.http.X-Host;
|
||
unset resp.http.Via;
|
||
unset resp.http.X-Varnish;
|
||
unset resp.http.Server;
|
||
set resp.http.X-Frame-Options = "SAMEORIGIN";
|
||
|
||
# strona serwisowa dla wybranych statusów
|
||
if (resp.status == 403 || resp.status == 404 || resp.status == 500 || resp.status == 503) {
|
||
return (synth(800, "Maintenance page"));
|
||
}
|
||
}
|
||
|
||
sub vcl_synth {
|
||
set resp.http.X-Cache = "SYNTH";
|
||
|
||
if (resp.status == 503 && req.restarts < 4) { return (restart); }
|
||
|
||
if (resp.status == 800) {
|
||
set resp.http.Content-Type = "text/html; charset=utf-8";
|
||
set resp.status = 404;
|
||
set resp.http.Cache-Control = "no-store, no-cache, must-revalidate, max-age=0";
|
||
synthetic({"<!DOCTYPE html>
|
||
<html><head><title>"} + resp.status + " " + resp.reason + {"</title></head>
|
||
<body><h1>Error "} + resp.status + {"</h1><p>"} + resp.reason + {"</p></body></html>"});
|
||
return (deliver);
|
||
}
|
||
}
|