Webmardi Fribourg, June 3rd 2014
© David Buchmann, Liip AG
apt-get update
apt-get install varnish
apt-get update
apt-get install varnish
/etc/default/varnish, replace port 6081 by port 80
service apache2 restart
service varnish restart
GET /path
HTTP/1.1 200 OK
Content-Type: text/html
<html>...</html>
https://twitter.com/stevelosh/status/372740571749572610
http://httpstatusdogs.com
Cache-Control: s-maxage=3600, max-age=900
Expires: Thu, 15 May 2014 08:00:00 GMT
Cache-Control: s-maxage=0, private, no-cache
Vary: Accept, Cookie
Last-Modified: Tue, 13 May 2014 08:13:20 GMT
If-Modified-Since: Tue, 13 May 2014 08:13:20 GMT
304 Not Modified
ETag: 82901821233
If-None-Match: 82901821233
304 Not Modified
wget -Sq --spider http://localhost/path.html
curl -o /dev/null -sD - http://cmf.lo/app_dev.php
HTTP/1.1 200 OK
Date: Wed, 12 May 2014 08:20:06 GMT
Cache-Control: no-cache
Vary: Accept-Language,Accept-Encoding
Content-Type: text/html; charset=UTF-8
// DefaultController::indexAction
$response = $this->render('::index.html.twig');
$response->setMaxAge(600);
$response->setPublic();
return $response;
fos_http_cache:
rules:
# login must not be cached
-
match:
path: ^/(login|login_check|logout)
controls:
private: true
max_age: 0
# Cache the homepage for 10 minutes
-
match:
path: ^/$
vary: Cookie
controls:
public: true
max_age: 600
# ...
# Cache everything else for 1 hour
-
match:
path: ^/
vary: Cookie
controls:
public: true
max_age: 3600
Clear the Symfony cache whenever you change values here
vcl_recv: entry point, lookup or pass
vcl_fetch: receive from backend, cache or not
vcl_deliver: remove headers not for client
vcl_hash: determine cache key
vcl_hit: found in cache, deliver or pass
vcl_miss: pass or fetch
vcl_pipe: do not alter request
vcl_error: define error page
Think carefully and test thoroughly
sub vcl_recv {
if (req.http.Cookie) {
if (req.url ~ "^/static") {
remove req.http.Cookie;
}
}
}
sub vcl_deliver {
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
}
fos_http_cache:
rules:
#...
reverse_proxy_ttl: 600
sub vcl_fetch {
...
if (beresp.http.X-Reverse-Proxy-TTL) {
C{
char *ttl;
ttl = VRT_GetHdr(sp, HDR_BERESP, "\024X-Reverse-Proxy-TTL:");
VRT_l_beresp_ttl(sp, atoi(ttl));
}C
unset beresp.http.X-Reverse-Proxy-TTL;
}
...
# app/config.yml
framework:
templating:
assets_version: v1
<link rel="stylesheet" href="/css/style.css?v1" type="text/css"/>
...
<script src="/js/scripts.js?v1"></script>
// CommentsController::postAction
// the page changed, send a purge request for this url
...
$path = $this->generate('page', array(
'id' => $post->getPage()->getId())
);
$cacheManager = $container->get('fos_http_cache.cache_manager');
$cacheManager->invalidatePath($path);
...
# app/config.yml
fos_http_cache:
proxy_client:
varnish:
servers: 4.4.4.11:80, 4.4.4.22:80
base_url: yourwebsite.com
# default.vcl
sub vcl_recv {
if (req.request == "PURGE") {
if (!client.ip ~ invalidators) {
error 405 "PURGE not allowed";
}
return (lookup);
}
}
sub vcl_hit {
if (req.request == "PURGE") {
purge;
error 200 "Purged";
}
}
sub vcl_miss { // same as hit
Group based caching
/** @var $cm CacheManageer */
$cm->tagResponse($response, array('comment-42'));
...
$cm->invalidateTags(array('comment-42'));
use FOS\HttpCacheBundle\Configuration\Tag;
class CommentController extends Controller
{
/**
* @Tag({"comments", "'comment-'~id"})
*/
public function commentAction($id)
{
// ...
ban("obj.http.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
);
# app/config/config.yml
framework:
esi: { enabled: true }
# app/config/routing.yml
_internal:
resource: "@FrameworkBundle/Resources/config/routing/internal.xml"
prefix: /_internal
Make sure either your webserver is only reachable by Varnish or add access restrictions for /_internal
{# index.html.twig #}
{% render 'DbuCoreBundle:Comments:comments'
with {}, {'standalone': true} %}
fos_http_cache:
rules:
# do not apply rules to _internal
-
match:
path: ^/_internal
# no controls
// UserController::showBoxAction
$response = $this->render(
'DbuCoreBundle:User:box.html.twig');
$response->setVary('Cookie', false);
$response->setMaxAge(0);
$response->setPrivate();
return $response;
// CommentsController::commentsAction
$response = $this->render(
'DbuCoreBundle:Comments:comments.html.twig',
array('comments' => $this->getComments()));
$response->setMaxAge(3600);
$response->setPublic();
return $response;
$cm = $container->get('fos_http_cache.cache_manager');
$kernel = $this->container->get('http_kernel');
$path = $kernel->generateInternalUri(
'DbuCoreBundle:Comments:comments'
);
$cm->invalidatePath($path);
Note: generateInternalUri is helpful but does not check if controller exists
$ app/console cache:clear --env=prod --no-debug
$ curl -H "Surrogate-Capability: abc=ESI/1.0" \
http://performance.lo/
...
<esi:include src="/_internal/secure/DbuCoreBundle%3AUser%3AshowLogin/none.html" onerror="continue" />
...
<esi:include src="/_internal/secure/DbuCoreBundle%3AComments%3Acomments/none.html" onerror="continue" />
...
sub vcl_recv {
set req.http.Surrogate-Capability = "abc=ESI/1.0";
# try to lookup even if there is a cookie
return (lookup);
}
sub vcl_fetch {
if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
unset beresp.http.Surrogate-Control;
set beresp.do_esi = true;
}
if (beresp.http.Vary) {
return (pass);
}
}
sub vcl_deliver {
if (! req.url ~ ".*\.(css|js|png)(\?.*)?$") {
set resp.http.Vary = "Cookie";
unset resp.http.Last-Modified;
}
}