Beim Aufruf einer Seite wird geguckt, ob die Seite im Cache vorhanden ist und wird bei einem Hit direkt vom Varnish ausgeliefert. Die Webseiten bleiben so lange im Cache liegen bis das mitgelieferte Verfallsdatum abgelaufen ist oder der Varnish neugestartet wurde. Wenn in der Zwischenzeit sich Daten auf der Seite verändert haben, werden diese erst nach dem Verfallsdatum oder nach einer Invalidierung der Seite im Cache angezeigt.
Für das invalidieren (Purge) der Cache-Seiten eignet sich das FriendsOfSymfony HttpCacheBundle, da es Varnish unterstützt und die Invalidierung sich mit Tags und Routen umsetzen lässt.
Wir wollen in diesem Beispiel eine einfache Lösung mit Cache-Tags zeigen.
Als erstes installieren wir Varnish auf dem Server; in unserem Beispiel auf einem Mac mit OS X
brew install varnish
Danach müssen wir die Varnish-Config an das Symfony-System anpassen. Die Config liegt Standardmässig hier: /usr/local/etc/varnish/default.vcl
vcl 4.0;
import std;
import directors;
# Default backend definition. Set this to point to your content server.
backend apache_server {
.host = "127.0.0.1";
.port = "8080";
}
acl purge_ip {
"localhost";
"127.0.0.1";
}
sub vcl_init{
new ws = directors.random();
ws.add_backend(apache_server, 1.0);
}
sub vcl_recv {
set req.backend_hint = ws.backend();
# Check correct header
if (req.method != "GET" &&
req.method != "HEAD" &&
req.method != "PUT" &&
req.method != "POST" &&
req.method != "TRACE" &&
req.method != "OPTIONS" &&
req.method != "PURGE" &&
req.method != "BAN" &&
req.method != "DELETE") {
/* Non-RFC2616 or CONNECT which is weird. */
return (pipe);
}
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;
}
}
if (req.http.Cookie) {
# Some generic cookie manipulation, useful for all templates that follow
# Remove the "has_js" cooki
set req.http.Cookie = ";" + req.http.Cookie;
set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
set req.http.Cookie = regsuball(req.http.Cookie, ";(PHPSESSID)=", "; \1=");
set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
# Remove any Google Analytics based cookies
set req.http.Cookie = regsuball(req.http.Cookie, "__utm.=[^;]+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "_ga=[^;]+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "_gat=[^;]+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "utmctr=[^;]+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "utmcmd.=[^;]+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "utmccn.=[^;]+(; )?", "");
# Remove DoubleClick offensive cookies
set req.http.Cookie = regsuball(req.http.Cookie, "__gads=[^;]+(; )?", "");
if (req.http.Cookie == "") {
// If there are no more cookies, remove the header to get page cached.
unset req.http.Cookie;
}
}
# Allow purging
if (req.method == "PURGE") {
if (!client.ip ~ purge_ip) {
#return(synth(405, "Not Found"));
return(synth(403, "Not allowed"));
}
return (purge);
}
# Allow banning
if (req.method == "BAN") {
if (!client.ip ~ purge_ip) {
return (synth(405, "Not allowed"));
}
if (req.http.X-Cache-Tags) {
ban("obj.http.X-Host ~ " + req.http.X-Host
+ " && obj.http.X-Url ~ " + req.http.X-Url
+ " && obj.http.content-type ~ " + req.http.X-Content-Type
+ " && obj.http.X-Cache-Tags ~ " + req.http.X-Cache-Tags
);
} else {
ban("obj.http.X-Host ~ " + req.http.X-Host
+ " && obj.http.X-Url ~ " + req.http.X-Url
+ " && obj.http.content-type ~ " + req.http.X-Content-Type
);
}
return (synth(200, "Banned"));
}
# Don't cache POST request
if (req.method ~ "POST") {
return (pass);
}
# Don't cache SonataAdmin admin area
if (req.url ~ "(^/app.php|^/app_dev.php|^)/admin" || req.url ~ "(^/app.php|^/app_dev.php|^)/(([a-z]{2})/admin)") {
return (pass);
}
# Don't cache FOS UserBundle security areas
if (req.url ~ "(^/app.php|^/app_dev.php|^)/(([a-z]{2}/|)(login|logout|login_check).*)") {
return (pass);
}
# Only cache GET or HEAD requests.
if (req.method != "GET" && req.method != "HEAD") {
return (pipe);
}
return (hash);
}
sub vcl_hash {
hash_data(req.url);
return (lookup);
}
sub vcl_backend_response {
set beresp.http.X-Url = bereq.url;
set beresp.http.X-Host = bereq.http.host;
return (deliver);
}
sub vcl_deliver {
# Add dev response, if X-Cache-Debug header exists
if (!resp.http.X-Cache-Debug) {
unset resp.http.X-Url;
unset resp.http.X-Host;
unset resp.http.X-Cache-Tags;
}else{
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
set resp.http.X-Cache-Hits = obj.hits;
} else {
set resp.http.X-Cache = "MISS";
}
}
}
sub vcl_synth {
set resp.http.Content-Type = "text/html; charset=utf-8";
set resp.http.Retry-After = "5";
synthetic ("Error");
return (deliver);
}
/usr/local/etc/varnish/default.vcl
Jetzt noch schnell Varnish starten.
$ sudo /usr/local/sbin/varnishd -f /usr/local/etc/varnish/default.vcl -a 127.0.0.1:80
Wir haben den Varnish auf den Port 80 gelegt und der Backend-Response soll vom Port 8080 kommen. Dafür muss noch der Apache eingerichtet werden.
<VirtualHost *:80>
# ändern in
<VirtualHost *:8080>
Symfony mit dem FOS HttpCacheBundle erweitert werden.
$ php composer require friendsofsymfony/http-cache-bundle
Nachdem Composer die Bundle-Dateien installiert hat, muss das Bundle noch im AppKernel registriert werden.
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
...
new FOS\HttpCacheBundle\FOSHttpCacheBundle(),
);
}
}
app/AppKernel.php
Das HttpCacheBundle braucht noch ein paar zusätzliche Configurations-Parameter, damit es mit dem Varnish kommuniziert und wir Tags setzten können.
fos_http_cache:
tags:
enabled: true
proxy_client:
default: varnish
varnish:
servers: 127.0.0.1:80
base_url: yourwebsite.com
app/config.yml
Wir haben Varnish jetzt installiert, den Apache für einen Reverse-Proxy konfiguriert und Symfony mit dem CacheBundle erweitert. Im zweiten Teil dieser Serie zeigen wir eine einfache Purge-Logik.