This week's topic is about cookies, a surprisingly challenging topic when it comes to caching.
Cookies are mechanisms in HTTP to keep track of state, and they're issued by the server under the form of a Set-Cookie response header. This is what a Set-Cookie header may look like.
Set-Cookie: sessionId=F6096889-6429-4F74-8398-12BCCF54DAF0;
path=/; samesite=lax; httponly
It contains a collection of key-value pairs, and also some extra attributes.
As soon as the client receives the Set-Cookie response, it will store the data in the local cookie store, and present that data upon subsequent requests to the server under the form of a cookie request header.
This is what a cookie looks like; the key-value pairs look surprisingly familiar:
Cookie: sessionId=F6096889-6429-4F74-8398-12BCCF54DAF0
When it comes to caching cookies, things get a lot more complicated, because cookies imply a level of personalization and represent private data, which is not cacheable by default. As soon as Varnish sees a Set-Cookie header coming from the origin, it will decide not to store the object in cache, and bypass the cache for the next request for about two minutes, or until the next response doesn't contain a Set-Cookie anymore. Similar things happen when a cookie request header is received by Varnish: Varnish will also bypass the cache.
It's really a challenge to strike the right balance between using functional cookies and still maintaining a decent level of caching, and the way we'll approach that challenge is by overriding standard VCL behavior and doing very context-specific cookie handling.
So here's a first example where we do find and replace actions on the cookie header:
vcl 4.1;
sub vcl_recv {
set req.http.Cookie = regsuball(req.http.Cookie, "has_js=[^;]+(; )?", "");
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.=[^;]+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "_gads=[^;]+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "_qc.=[^;]+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "_atuv.=[^;]+(; )?", "");
set req.http.Cookie = regsuball(req.http.Cookie, "^;\s*", "");
if (req.http.Cookie ~ "^\s*$") {
unsetn req.http.Cookie;
}
}
We're looking for specific patterns that match the tracking cookies that we want to get rid of, and then we remove them because the server doesn't really need them. There's also another way of approaching it using the cookie module, and we have a very clean API that allows us to filter out specific cookies or patterns of cookies to reach the same goal. We can also approach it from an entirely different angle and remove every single cookie, except the ones that we need. In this case, we'll only use the session ID cookie, and all the rest will be removed. Of course, if we use the Cookie VMOD, there is a clean API to do this.
Instead of looking for individual cookies, we can also look at cookie usage for specific URL endpoints. In this example, we're removing cookies for every single URL, except for the admin panel, or for subordinate resources of the admin panel where we know we're going to need the cookies.
vcl 4.1;
sub vcl_recv {
if(req.url !~ "^/admin(/.*|$)") {
unset req.http.Cookie;
}
}
Cookies in Varnish is quite a challenging topic, there's so much more to be told, and this was only the tip of the iceberg. Stay tuned for more videos around cookie-specific examples and integrations, but for now please remember to remove all the cookies you don't need, or just keep the ones you do need and inspect them on a per URL, or a per URL pattern basis.
We'll be back next Tuesday with more tech, presented in two minutes or less.