Search code examples
varnishvarnish-vcl

How to pass data (header?) through in Varnish 4.0


I'm using devicedetect.vcl to send the X-UA-Device header to my app, so it knows which layout to render. The possible values that varnish will set for this header are mobile or desktop.

On the way out, this header gets transformed to Vary: User-Agent.

Now, as a separate, isolated project, I need to set another header on the resp object (which gets sent to our Golang proxy before it gets sent to the client). This header will be called X-Analytics-Device and will have the possible values of bot, mobile, tablet, or desktop.

The backend server does not need to do anything with X-Analytics-Device. Only our Go proxy will parse and then remove this header before sending it to the client.

The problem is, I need to set the X-Analytics-Device header based on the results of the subroutine call devicedetect;, which is in vcl_recv. I need to ultimately set it on resp which is in vcl_deliver, and I need to know the best way to pass the data.

The only real way I can think of that might work (based on my limited understanding of Varnish), is that I need to set some other header, and access it later.

Perhaps something like this (I left out bot for now):

if (req.http.X-UA-Device ~ "^mobile") {
  set req.http.X-UA-Device        = "mobile";
  set req.http.X-Analytics-Device = "mobile";

} elseif (req.http.X-UA-Device ~ "^tablet") {
  set req.http.X-UA-Device        = "desktop";
  set req.http.X-Analytics-Device = "tablet";

} else {
  set req.http.X-UA-Device        = "desktop";
  set req.http.X-Analytics-Device = "desktop";
}

After this... I don't know. Do I need to set it like this in vcl_deliver?

set resp.http.X-Analytics-Device = req.http.X-Analytics-Device;

How does it get passed from the resp to the req? What happens if it's a hit or a miss? Does that matter? Is this going to try to cache this header in varnish (which it shouldnt be, obviously)?

My main fear with doing it this way is that there are so many moving pieces I just don't know the best way.

The end result is that... EVERY request needs to check the device, and on the way out it needs to set the header, without that value being cached along with the data in varnish, and while it doesnt hurt to send it to the backend, its not needed.

Here's my full VCL, before I added the pseudo-code lines above.

vcl 4.0;

backend default {
  .host = "127.0.0.1";
  .port = "8080";
}

import std;

include "purge.vcl";
include "devicedetect.vcl";

acl purge {
  "localhost";
  "127.0.0.1";
  "10.0.0.0"/8;
}

sub vcl_recv {
  call devicedetect;
  if (req.http.X-UA-Device ~ "^mobile") {
    set req.http.X-UA-Device = "mobile";
  } else {
    set req.http.X-UA-Device = "desktop";
  }

  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.method !~ "^(GET|HEAD|PUT|POST|OPTIONS|DELETE)$") {
    return (synth(405));
  }

  # never cache anything except GET/HEAD
  if (req.method != "GET" && req.method != "HEAD") {
    return (pass);
  }

  # don't cache images or assets
  if (req.url ~ "\.(js|css|jpg|jpeg|png|gif|ico|tiff|tif|bmp|svg)$") {
    return (pass);
  }

  # fix up the request
  unset req.http.cookie;
  return (hash);
}

sub vcl_backend_response {
  set beresp.do_stream = false;

  # device detect
  if (bereq.http.X-UA-Device) {
    if (!beresp.http.Vary) { # no Vary at all
      set beresp.http.Vary = "X-UA-Device";
    } elseif (beresp.http.Vary !~ "X-UA-Device") { # add to existing Vary
      set beresp.http.Vary = beresp.http.Vary + ", X-UA-Device";
    }
  }

  # bypass cache for files > 5 MB
  if (std.integer(beresp.http.Content-Length, 0) > 5242880) {
    set beresp.uncacheable = true;
    set beresp.ttl = 120s;
    return (deliver);
  }

  # catch obvious reasons we can't cache
  if (beresp.http.Set-Cookie) {
    set beresp.ttl = 0s;
  }

  # avoid caching error responses (1m grace period)
  if (beresp.status >= 500) {
    set beresp.ttl = 1m;
    return (deliver);
  }

  # set times
  set beresp.ttl = 24h;
  set beresp.grace = 4h;
  return (deliver);
}

sub vcl_deliver {
  # device detect
  if ((req.http.X-UA-Device) && (resp.http.Vary)) {
    set resp.http.Vary = regsub(resp.http.Vary, "X-UA-Device", "User-Agent");
  }

  # remove junk headers
  unset resp.http.Server;
  unset resp.http.Via;
  unset resp.http.X-Powered-By;
  unset resp.http.X-Runtime;
  unset resp.http.X-Varnish;

  if (obj.hits > 0) {
    set resp.http.X-Cache = "HIT";
  } else {
    set resp.http.X-Cache = "MISS";
  }
}

Solution

  • This link actually perfectly clarifies and answers all the things I was failing to articulate... https://info.varnish-software.com/blog/adding-headers-gain-insight-vcl

    The answer is to shovel all the bits of data you need into the req headers in vcl_recv, and then copy them over to the response in vcl_deliver.

    He states the following, about why it won't get cached:

    Since the req object is not delivered to the client we need to copy the data from the req object to resp. We do this when we deliver it. If you do it in vcl_backend_response the headers will be stored in the cache and this might not be what you want.