diff --git a/etc/varnish/ezcluster.vcl b/etc/varnish/ezcluster.vcl index 0ca3b5a..34e8d03 100644 --- a/etc/varnish/ezcluster.vcl +++ b/etc/varnish/ezcluster.vcl @@ -4,6 +4,7 @@ vcl 4.0; # new b = directors.round_robin() # b.add_backend(node1); #} +include "ezpublish.vcl"; backend default { .host = "127.0.0.1"; @@ -30,10 +31,6 @@ backend backend_setup { .first_byte_timeout = 600s; .between_bytes_timeout = 600s; } -acl purge { -"localhost"; -"127.0.0.1"; -} acl elb { "10.0.0.0"/24; @@ -101,12 +98,14 @@ sub vcl_recv } } if (req.method == "PURGE") { - if (!client.ip ~ purge) { - return (synth(405, "Not allowed.")); + if (!client.ip ~ invalidators) { + return (synth(405, "Not allowed")); } - ban("req.url ~ " + req.url + " && req.http.host == " + req.http.host); - return (synth(200, "Purged.")); + return (purge); } + // Trigger cache purge if needed + call ez_purge; + if (req.method != "GET" && req.method != "HEAD" && req.method != "PUT" && @@ -118,11 +117,6 @@ sub vcl_recv /* Non-RFC2616 or CONNECT which is weird. */ return (pass); } - # unknown problems with .gz files and yum - # [Errno 14] Downloaded more than max size for http://packages.xrow.com/redhat/6/repodata/primary.xml.gz - if (req.url ~ "\.gz$") { - return (pass); - } if (req.http.Cookie && (req.http.Cookie ~ "eZSESSID" || req.http.Cookie ~ "PHPSESSID")) { return (pass); @@ -141,6 +135,10 @@ sub vcl_recv # return (pass); #} set req.http.Surrogate-Capability = "abc=ESI/1.0"; + + // Retrieve client user hash and add it to the forwarded request. + call ez_user_hash; + return (hash); } sub vcl_hash { @@ -157,6 +155,11 @@ sub vcl_hash { return (lookup); } sub vcl_backend_response { + if (bereq.http.accept ~ "application/vnd.fos.user-context-hash" + && beresp.status >= 500 + ) { + return (abandon); + } set beresp.http.X-Backend = beresp.backend.name; if (beresp.http.Surrogate-Control ~ "ESI/1.0") { @@ -195,6 +198,27 @@ sub vcl_backend_response { } 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" + ) { + 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; + if( resp.http.Content-Type && resp.http.Content-Type ~ "text/html" ) { unset resp.http.Last-Modified; diff --git a/etc/varnish/ezpublish.vcl b/etc/varnish/ezpublish.vcl new file mode 100644 index 0000000..71adff1 --- /dev/null +++ b/etc/varnish/ezpublish.vcl @@ -0,0 +1,87 @@ +// ACL for invalidators IP +acl invalidators { + "127.0.0.1"; +} + +// ACL for debuggers IP +acl debuggers { + "127.0.0.1"; +} + +// Handle purge +// You may add FOSHttpCacheBundle tagging rules +// See http://foshttpcache.readthedocs.org/en/latest/varnish-configuration.html#id4 +sub ez_purge { + + if (req.method == "BAN") { + if (!client.ip ~ invalidators) { + return (synth(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; + } + return (synth(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 + ) + ) { + return (synth(400)); + } + + if (req.restarts == 0 && (req.method == "GET" || req.method == "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 (hash); + } + } + + // 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 (hash); + } +} +