Skip to content

Commit

Permalink
Merge pull request ezsystems#198 from ezsystems/impl_EZP-22400_FOSHtt…
Browse files Browse the repository at this point in the history
…pCacheBundle

EZP-22400: Use FOSHttpCacheBundle
  • Loading branch information
lolautruche committed Oct 30, 2014
2 parents a794d42 + 0f2b148 commit 80df487
Show file tree
Hide file tree
Showing 5 changed files with 435 additions and 1 deletion.
4 changes: 3 additions & 1 deletion composer.json
Expand Up @@ -34,7 +34,9 @@
"hautelook/templated-uri-bundle": "~1.0 | ~2.0",
"doctrine/dbal": "~2.5@rc",
"doctrine/doctrine-bundle": "~1.3@beta",
"liip/imagine-bundle": "~1.0"
"liip/imagine-bundle": "~1.0",
"symfony/expression-language": "~2.4",
"sensio/framework-extra-bundle": "~3.0"
},
"require-dev": {
"behat/behat": "3.0.*",
Expand Down
14 changes: 14 additions & 0 deletions doc/varnish/varnish.md
@@ -0,0 +1,14 @@
# eZ Publish Varnish configuration

## Prerequisites
* A working Varnish 3 or Varnish 4 setup.

## Recommended VCL base files
For Varnish to work properly with eZ, you'll need to use one of the provided files as a basis:

* [eZ 5.4+ / 2014.09+ with Varnish 3](vcl/varnish3.vcl)
* [eZ 5.4+ / 2014.09+ with Varnish 4](vcl/varnish4.vcl)

> **Note:** Http cache management is done with the help of [FOSHttpCacheBundle](http://foshttpcachebundle.readthedocs.org/).
One may need to tweak their VCL further on according to [FOSHttpCache documentation](http://foshttpcache.readthedocs.org/en/latest/varnish-configuration.html)
in order to use features supported by it.
209 changes: 209 additions & 0 deletions doc/varnish/vcl/varnish3.vcl
@@ -0,0 +1,209 @@
// Varnish 3 style - eZ 5.4+ / 2014.09+
// Complete VCL example

// Our Backend - Assuming that web server is listening on port 80
// Replace the host to fit your setup
backend ezpublish {
.host = "127.0.0.1";
.port = "80";
}

// ACL for invalidators IP
acl invalidators {
"127.0.0.1";
"192.168.0.0"/16;
}

// ACL for debuggers IP
acl debuggers {
"127.0.0.1";
"192.168.0.0"/16;
}

// Called at the beginning of a request, after the complete request has been received
sub vcl_recv {

// Set the backend
set req.backend = ezpublish;

// Advertise Symfony for ESI support
set req.http.Surrogate-Capability = "abc=ESI/1.0";

// Add a unique header containing the client address (only for master request)
// Please note that /_fragment URI can change in Symfony configuration
if (!req.url ~ "^/_fragment") {
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;
}
}

// Trigger cache purge if needed
call ez_purge;

// Don't cache requests other than GET and HEAD.
if (req.request != "GET" && req.request != "HEAD") {
return (pass);
}

// Normalize the Accept-Encoding headers
if (req.http.Accept-Encoding) {
if (req.http.Accept-Encoding ~ "gzip") {
set req.http.Accept-Encoding = "gzip";
} elsif (req.http.Accept-Encoding ~ "deflate") {
set req.http.Accept-Encoding = "deflate";
} else {
unset req.http.Accept-Encoding;
}
}

// Don't cache Authenticate & Authorization
// You may remove this when using REST API with basic auth.
if (req.http.Authenticate || req.http.Authorization) {
if (client.ip ~ debuggers) {
set req.http.X-Debug = "Not Cached according to configuration (Authorization)";
}
return(pass);
}

// Do a standard lookup on assets
// Note that file extension list below is not extensive, so consider completing it to fit your needs.
if (req.url ~ "\.(css|js|gif|jpe?g|bmp|png|tiff?|ico|img|tga|wmf|svg|swf|ico|mp3|mp4|m4a|ogg|mov|avi|wmv|zip|gz|pdf|ttf|eot|wof)$") {
return (lookup);
}

// Retrieve client user hash and add it to the forwarded request.
call ez_user_hash;

// If it passes all these tests, do a lookup anyway.
return (lookup);
}

// Called when the requested object has been retrieved from the backend
sub vcl_fetch {

if (req.restarts == 0
&& req.http.accept ~ "application/vnd.fos.user-context-hash"
&& beresp.status >= 500
) {
error 503 "Hash error";
}

// Optimize to only parse the Response contents from Symfony
if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
unset beresp.http.Surrogate-Control;
set beresp.do_esi = true;
}

// Respect the Cache-Control=private header from the backend
if (beresp.http.Cache-Control ~ "no-cache|no-store|private") {
set beresp.ttl = 120s;
return (hit_for_pass);
}

return (deliver);
}

// Handle purge
// You may add FOSHttpCacheBundle tagging rules
// See http://foshttpcache.readthedocs.org/en/latest/varnish-configuration.html#id4
sub ez_purge {

if (req.request == "BAN") {
if (!client.ip ~ invalidators) {
error 405 "Method not allowed";
}

if (req.http.X-Location-Id) {
ban( "obj.http.X-Location-Id ~ " + req.http.X-Location-Id);
if (client.ip ~ debuggers) {
set req.http.X-Debug = "Ban done for content connected to LocationId " + req.http.X-Location-Id;
}
error 200 "Banned";
}
}
}

// Sub-routine to get client user hash, for context-aware HTTP cache.
sub ez_user_hash {

// Prevent tampering attacks on the hash mechanism
if (req.restarts == 0
&& (req.http.accept ~ "application/vnd.fos.user-context-hash"
|| req.http.x-user-hash
)
) {
error 400;
}

if (req.restarts == 0 && (req.request == "GET" || req.request == "HEAD")) {
// Anonymous user => Set a hardcoded anonymous hash
if (req.http.Cookie !~ "eZSESSID" && !req.http.authorization) {
set req.http.X-User-Hash = "38015b703d82206ebc01d17a39c727e5";
}
// Pre-authenticate request to get shared cache, even when authenticated
else {
set req.http.x-fos-original-url = req.url;
set req.http.x-fos-original-accept = req.http.accept;
set req.http.x-fos-original-cookie = req.http.cookie;
// Clean up cookie for the hash request to only keep session cookie, as hash cache will vary on cookie.
set req.http.cookie = ";" + req.http.cookie;
set req.http.cookie = regsuball(req.http.cookie, "; +", ";");
set req.http.cookie = regsuball(req.http.cookie, ";(eZSESSID[^=]*)=", "; \1=");
set req.http.cookie = regsuball(req.http.cookie, ";[^ ][^;]*", "");
set req.http.cookie = regsuball(req.http.cookie, "^[; ]+|[; ]+$", "");

set req.http.accept = "application/vnd.fos.user-context-hash";
set req.url = "/_fos_user_context_hash";

// Force the lookup, the backend must tell how to cache/vary response containing the user hash

return (lookup);
}
}

// Rebuild the original request which now has the hash.
if (req.restarts > 0
&& req.http.accept == "application/vnd.fos.user-context-hash"
) {
set req.url = req.http.x-fos-original-url;
set req.http.accept = req.http.x-fos-original-accept;
set req.http.cookie = req.http.x-fos-original-cookie;

unset req.http.x-fos-original-url;
unset req.http.x-fos-original-accept;
unset req.http.x-fos-original-cookie;

// Force the lookup, the backend must tell not to cache or vary on the
// user hash to properly separate cached data.

return (lookup);
}
}

sub vcl_deliver {
// On receiving the hash response, copy the hash header to the original
// request and restart.
if (req.restarts == 0
&& resp.http.content-type ~ "application/vnd.fos.user-context-hash"
&& resp.status == 200
) {
set req.http.x-user-hash = resp.http.x-user-hash;

return (restart);
}

// If we get here, this is a real response that gets sent to the client.

// Remove the vary on context user hash, this is nothing public. Keep all
// other vary headers.
set resp.http.Vary = regsub(resp.http.Vary, "(?i),? *x-user-hash *", "");
set resp.http.Vary = regsub(resp.http.Vary, "^, *", "");
if (resp.http.Vary == "") {
unset resp.http.Vary;
}

// Sanity check to prevent ever exposing the hash to a client.
unset resp.http.x-user-hash;
}

0 comments on commit 80df487

Please sign in to comment.