enable varnish

enable varnish

vcl 4.0;
## Based on: https://github.com/mattiasgeniar/varnish-4.0-configuration-templates/blob/master/default.vcl
# Corrected & improved for 4.0.2 by jnerin@gmail.com
import std;
import directors;

include "badbot.vcl";

backend alexa1 {
        .host = "192.168.16.3";
        .port = "8080";

        .max_connections = 300; # That's it
        .first_byte_timeout     = 300s;   # How long to wait before we receive a first byte from our backend?
        .connect_timeout        = 5s;     # How long to wait for a backend connection?
        .between_bytes_timeout  = 2s;     # How long to wait between bytes received from our backend?

#        .probe = {
#	       .request =
#                 "GET /robots.txt HTTP/1.1"
#                 "Host: admin.platform.bricks.20steps.de"
#                 "Connection: close";
#            .interval = 5s;
#            .timeout = 1s;
#            .window = 5;
#            .threshold = 3;
#        }
}

#backend alexa2 {
#        .host = "192.168.13.20";
#        .port = "8080";
#
#        .max_connections = 300; # That's it
#        .first_byte_timeout     = 300s;   # How long to wait before we receive a first byte from our backend?
#        .connect_timeout        = 5s;     # How long to wait for a backend connection?
#        .between_bytes_timeout  = 2s;     # How long to wait between bytes received from our backend?
#
#        .probe = {
#            .request =
#                 "GET /robots.txt HTTP/1.1"
#                 "Host: admin.platform.bricks.20steps.de"
#                 "Connection: close";
#            .interval = 5s;
#            .timeout = 1s;
#            .window = 5;
#            .threshold = 3;
#        }
#}

#backend alexaj1 {
#        .host = "192.168.14.17";
#        .port = "8080";
#
#        .max_connections = 300; # That's it
#        .first_byte_timeout     = 300s;   # How long to wait before we receive a first byte from our backend?
#        .connect_timeout        = 5s;     # How long to wait for a backend connection?
#        .between_bytes_timeout  = 2s;     # How long to wait between bytes received from our backend?
#
#        .probe = {
#            .request =
#                 "GET /robots.txt HTTP/1.1"
#                 "Host: admin.platform.bricks.20steps.de"
#                 "Connection: close";
#            .interval = 5s;
#            .timeout = 1s;
#            .window = 5;
#            .threshold = 3;
#        }
#}

acl invalidators {
  # ACL we'll use later to allow purges
  "localhost";
  "127.0.0.1";
  "::1";
  "192.168.0.0"/16;
}

sub vcl_init {
# Called when VCL is loaded, before any requests pass through it. Typically used to initialize VMODs.

  include "director.vcl";

}

sub vcl_recv {
# Called at the beginning of a request, after the complete request has been received and parsed. Its purpose is to decide whether or not to serve the request, how to do it, and, if applicable, which backend to use.
# also used to modify the request

  call badbot;

  ## REQUEST NORMALIZATION
  # Normalize the header, remove the port (in case you're testing this on various TCP ports)
  set req.http.Host = regsub(req.http.Host, ":[0-9]+", "");

  # Normalize the query arguments except for phpmyadmin
  if (req.http.Host ~ "phpmyadmin") {
     # no sorting here
  } else if (req.url !~ "wp-admin") {
    set req.url = std.querysort(req.url);
  }

  if (req.restarts == 0) {
    if (req.http.X-Forwarded-For) { # set or append the client.ip to X-Forwarded-For header
      set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip;
    } else {
      set req.http.X-Forwarded-For = client.ip;
    }
  }

  # For mod_pagespeed (cp. https://developers.google.com/speed/pagespeed/module/downstream-caching)
  # Tell PageSpeed not to use optimizations specific to this request.
  set req.http.PS-CapabilityList = "fully general optimizations only";

  # Don't allow external entities to force beaconing.
  unset req.http.PS-ShouldBeacon;

  ## letsencrypt
  if (req.url ~ "acme-challenge") {
     return (pipe);
  }

  ## http to https

  if (
     (req.http.Host ~ "alexa.20steps.de")
      && req.http.X_FORWARDED_PROTO !~ "(?i)https") {
    if (
      # req.url !~ "wp-admin"
      # && req.url !~ "wp-login"
      # && req.url !~ "secure"
      # && req.url !~ "well-known"
      req.url !~ "well-known"
      ) {
        return (synth(750, ""));
     }
  }

  ## HOST MODIFICATION AND BACKEND SELECTION

  # default backend director
  set req.backend_hint = zone.backend(); # send all traffic to the apache_director

  # direct access to backend depending on Header
  if (req.http.X-Server-Select == "alexa1") {
     set req.backend_hint = alexa1;
  } else if (req.http.X-Server-Select == "alexa2") {
     # set req.backend_hint = alexa2;
  } else if (req.http.X-Server-Select == "alexaj1") {
     #set req.backend_hint = alexaj1;
  }

  if (req.http.Host ~ "^job\.") {
     #set req.backend_hint = alexaj1;
  }

  # modify host and/or backend depending on url for further processing

  # Normalize Accept-Encoding header
  # straight from the manual: https://www.varnish-cache.org/docs/3.0/tutorial/vary.html
  # TODO: Test if it's still needed, Varnish 4 now does this by itself if http_gzip_support = on
  # https://www.varnish-cache.org/docs/trunk/users-guide/compression.html
  # https://www.varnish-cache.org/docs/trunk/phk/gzip.html
  if (req.http.Accept-Encoding) {
    if (req.url ~ "\.(avi|bmp|bz2|cssgz|doc|divx|eot|flv|gif|gz|ico|jpeg|jpg|jsgz|mp3|mp4|mpg|mpeg|mk[av]|mov|ogg|ogv|pdf|png|qt|rar|swf|tbz|woff|xz|zip|webp)$") {
      # No point in compressing these
      unset req.http.Accept-Encoding;
    } elsif (req.http.Accept-Encoding ~ "gzip") {
      set req.http.Accept-Encoding = "gzip";
    } elsif (req.http.Accept-Encoding ~ "deflate") {
      set req.http.Accept-Encoding = "deflate";
    } else {
      # unkown algorithm
      unset req.http.Accept-Encoding;
    }
  }

  ## HANDLING DEPENDING ON HTTP METHOD

  # Provide PURGE HTTP method
  if (req.method == "PURGE") {
    if (!client.ip ~ invalidators) { # purge is the ACL defined at the begining
      # Not from an allowed IP? Then die with an error.
      return (synth(405, "Not allowed"));
    }
    # If you got this stage (and didn't error out above), purge the cached result
    return (purge);
  }

  if (req.method == "BAN") {
     if (!client.ip ~ invalidators) {
         return (synth(405, "Not allowed"));
      }

      if (req.http.X-Cache-Tags) {
            ban("obj.http.X-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
            );
      } else {
	    ban("obj.http.X-Host ~ " + req.http.X-Host
                + " && obj.http.X-Url ~ " + req.http.X-Url
         	+ " && obj.http.content-type ~ " + req.http.X-Content-Type
      	    );
      }

      return (synth(200, "Banned"));
  }

  # Only deal with "normal" types
  if (req.method != "GET" &&
      req.method != "HEAD" &&
      req.method != "PUT" &&
      req.method != "POST" &&
      req.method != "TRACE" &&
      req.method != "OPTIONS" &&
      req.method != "PATCH" &&
      req.method != "DELETE") {
    /* Non-RFC2616 or CONNECT which is weird. */
    return (pipe);
  }

  # Implementing websocket support (https://www.varnish-cache.org/docs/4.0/users-guide/vcl-example-websockets.html)
  if (req.http.Upgrade ~ "(?i)websocket") {
    return (pipe);
  }

  # Only cache GET or HEAD requests. This makes sure the POST requests are always passed.
  if (req.method != "GET" && req.method != "HEAD") {
    return (pass);
  }

  ## COOKIE HANDLING

  # Some generic URL manipulation, useful for all templates that follow
  # First remove the Google Analytics added parameters, useless for our backend
  if (req.url ~ "(\?|&)(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=") {
    set req.url = regsuball(req.url, "&(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", "");
    set req.url = regsuball(req.url, "\?(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", "?");
    set req.url = regsub(req.url, "\?&", "?");
    set req.url = regsub(req.url, "\?$", "");
  }

  # Strip hash, server doesn't need it.
  if (req.url ~ "\#") {
    set req.url = regsub(req.url, "\#.*$", "");
  }

  # Strip a trailing ? if it exists
  if (req.url ~ "\?$") {
    set req.url = regsub(req.url, "\?$", "");
  }

  # Some generic cookie manipulation, useful for all templates that follow
  # Remove the "has_js" cookie
  set req.http.Cookie = regsuball(req.http.Cookie, "has_js=[^;]+(; )?", "");

  # Remove any Google Analytics based cookies
  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, "utmctr=[^;]+(; )?", "");
  set req.http.Cookie = regsuball(req.http.Cookie, "utmcmd.=[^;]+(; )?", "");
  set req.http.Cookie = regsuball(req.http.Cookie, "utmccn.=[^;]+(; )?", "");

  # Remove DoubleClick offensive cookies
  set req.http.Cookie = regsuball(req.http.Cookie, "__gads=[^;]+(; )?", "");

  # Remove the Quant Capital cookies (added by some plugin, all __qca)
  set req.http.Cookie = regsuball(req.http.Cookie, "__qc.=[^;]+(; )?", "");

  # Remove the AddThis cookies
  set req.http.Cookie = regsuball(req.http.Cookie, "__atuv.=[^;]+(; )?", "");

  # Remove a ";" prefix in the cookie if present
  set req.http.Cookie = regsuball(req.http.Cookie, "^;\s*", "");

  # Are there cookies left with only spaces or that are empty?
  if (req.http.cookie ~ "^\s*$") {
    unset req.http.cookie;
  }

  ## Allow for refreshing using <shift>+<reload> in browser
  if (req.http.Cache-Control ~ "(?i)no-cache" && client.ip ~ invalidators) {
    if (! (req.http.Via || req.http.User-Agent ~ "(?i)bot" || req.http.X-Purge)) {
      #set req.hash_always_miss = true; # Doesn't seems to refresh the object in the cache
      #return(purge); # Couple this with restart in vcl_purge and X-Purge header to avoid loops
    }
  }

  ## DO NOT CACHE APPLE APP SITE ASSOCIATIONS
  if (req.url ~ "apple-app-site-association") {
     return (pass);
  }

  ## DO NOT CACHE DEPENDING ON HOST/URL

  # do not cache depending on host/url
  if (
    req.url ~ "^/(wp-admin|admin)"
    || req.url ~ "(secure)"
    || req.url ~ "(raten|rechner|eAntrag)"
  ) {
      return (pass);
  }

  ## REMOVE COOKIES AS PREREQUESTITE FOR CACHING DEPENDING ON TYPE/URL/HOST

  # Remove all cookies for static files
  # A valid discussion could be held on this line: do you really need to cache static files that don't cause load? Only if you have memory left.
  # Sure, there's disk I/O, but chances are your OS will already have these files in their buffers (thus memory).
  # Before you blindly enable this, have a read here: http://mattiasgeniar.be/2012/11/28/stop-caching-static-files/
  if (req.url ~ "^[^?]*\.(avi|bmp|bz2|css|cssgz|doc|divx|eot|flv|gif|gz|ico|jpeg|jpg|js|jsgz|less|mp3|mp4|mpg|mpeg|mk[av]|mov|ogg|ogv|pdf|png|qt|rar|rtf|swf|txt|tbz|woff|xml|xz|zip|webp)$") {
    unset req.http.Cookie;
    return (hash);
  } else if (req.url ~ "timthumb.php") {
    unset req.http.Cookie;
    return (hash);
  }

  # Send Surrogate-Capability headers to announce ESI support to backend
  set req.http.Surrogate-Capability = "key=ESI/1.0";

  # allow caching even when using Authorization
  #if (req.http.Authorization) {
  #  # Not cacheable by default
  #  return (pass);
  #}

  return (hash);
}

sub vcl_pipe {
# Called upon entering pipe mode. In this mode, the request is passed on to the backend, and any further data from both the client and backend is passed on unaltered until either end closes the connection. Basically, Varnish will degrade into a simple TCP proxy, shuffling bytes back and forth. For a connection in pipe mode, no other VCL subroutine will ever get called after vcl_pipe.

  # Note that only the first request to the backend will have
  # X-Forwarded-For set.  If you use X-Forwarded-For and want to
  # have it set for all requests, make sure to have:
  # set bereq.http.connection = "close";
  # here.  It is not set by default as it might break some broken web
  # applications, like IIS with NTLM authentication.

  #set bereq.http.Connection = "Close";

  # Implementing websocket support (https://www.varnish-cache.org/docs/4.0/users-guide/vcl-example-websockets.html)
       if (req.http.upgrade) {
          set bereq.http.upgrade = req.http.upgrade;
       }

  return (pipe);
}

sub vcl_pass {
# Called upon entering pass mode. In this mode, the request is passed on to the backend, and the backend's response is passed on to the client, but is not entered into the cache. Subsequent requests submitted over the same client connection are handled normally.

  # return (pass);
}

# The data on which the hashing will take place
sub vcl_hash {
# Called after vcl_recv to create a hash value for the request. This is used as a key to look up the object in Varnish.

  hash_data(req.url);

  if (req.http.host) {
    hash_data(req.http.host);
  } else {
    hash_data(server.ip);
  }

  # hash cookies for requests that have them
  if (req.http.Cookie) {
    hash_data(req.http.Cookie);
  }

}

sub vcl_hit {
  # Called when a cache lookup is successful.

  # mod_pagespeed
  # 5% of the time ignore that we got a cache hit and send the request to the
  # backend anyway for instrumentation.
  if (std.random(0, 100) < 5) {
    set req.http.PS-ShouldBeacon = "futuresteps25";
    return (pass);
  }

  if (obj.ttl >= 0s) {
    # A pure unadultered hit, deliver it
    return (deliver);
  }

  # https://www.varnish-cache.org/docs/trunk/users-guide/vcl-grace.html
  # When several clients are requesting the same page Varnish will send one request to the backend and place the others on hold while fetching one copy from the backend. In some products this is called request coalescing and Varnish does this automatically.
  # If you are serving thousands of hits per second the queue of waiting requests can get huge. There are two potential problems - one is a thundering herd problem - suddenly releasing a thousand threads to serve content might send the load sky high. Secondly - nobody likes to wait. To deal with this we can instruct Varnish to keep the objects in cache beyond their TTL and to serve the waiting requests somewhat stale content.

#  if (!std.healthy(req.backend_hint) && (obj.ttl + obj.grace > 0s)) {
#    return (deliver);
#  } else {
#    return (fetch);
#  }

  # We have no fresh fish. Lets look at the stale ones.
  if (std.healthy(req.backend_hint)) {
    # Backend is healthy. Limit age to 10s.
        if (obj.ttl + 10s > 0s) {
            #set req.http.grace = "normal(limited)";
            return (deliver);
        } else {
            # No candidate for grace. Fetch a fresh object.
	    return(fetch);
       }
  } else {
    # backend is sick - use full grace
        if (obj.ttl + obj.grace > 0s) {
            #set req.http.grace = "full";
      return (deliver);
    } else {
      # no graced object.
      return (fetch);
    }
  }

  # fetch & deliver once we get the result
  return (fetch);  # Dead code, keep as a safeguard
}

sub vcl_miss {
# Called after a cache lookup if the requested document was not found in the cache. Its purpose is to decide whether or not to attempt to retrieve the document from the backend, and which backend to use.

  # mod_pagespeed
  # Instrument 25% of cache misses.
  if (std.random(0, 100) < 25) {
    set req.http.PS-ShouldBeacon = "futuresteps25";
    return (pass);
  }

  return (fetch);
}

# Handle the HTTP request coming from our backend
sub vcl_backend_response {
# Called after the response headers has been successfully retrieved from the backend.

  # Set ban-lurker friendly custom headers
  set beresp.http.X-Url = bereq.url;
  set beresp.http.X-Host = bereq.http.host;

  if (beresp.status >= 400 || beresp.status >= 500) {
     set beresp.uncacheable = true;
     return (deliver);
  }

  # Pause ESI request and remove Surrogate-Control header
  if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
    unset beresp.http.Surrogate-Control;
    set beresp.do_esi = true;
  }

  # send json as text/html instead of application/json as MSIE wants to save the "file" otherwise
  if (bereq.url ~ "bricks/api") {
     # set beresp.http.Content-Type = "text/html; charset=utf-8";
  }

  # Enable cache for all static files
  # The same argument as the static caches from above: monitor your cache size, if you get data nuked out of it, consider giving up the static file cache.
  # Before you blindly enable this, have a read here: http://mattiasgeniar.be/2012/11/28/stop-caching-static-files/
  if (bereq.url ~ "^[^?]*\.(avi|bmp|bz2|css|cssgz|doc|divx|eot|flv|gif|gz|ico|jpeg|jpg|js|jsgz|less|mp3|mp4|mpg|mpeg|mk[av]|mov|ogg|ogv|pdf|png|qt|rar|rtf|swf|txt|tbz|woff|xml|xz|zip|webp)$") {
    set beresp.http.cache-control = "max-age=604800";
    unset beresp.http.set-cookie;
    unset beresp.http.expires;
    set beresp.http.cachable = "1";
  }

  # Large static files are delivered directly to the end-user without
  # waiting for Varnish to fully read the file first.
  # Varnish 4 fully supports Streaming, so use streaming here to avoid locking.
  if (bereq.url ~ "^[^?]*\.(mp[34]|rar|tar|tgz|gz|wav|zip|bz2|xz|7z|avi|mov|ogm|mpe?g|mk[av])(\?.*)?$") {
    unset beresp.http.set-cookie;
    set beresp.ttl = 3600s;
    unset beresp.http.expires;
    set beresp.http.cache-control = "max-age=3600";
    set beresp.http.cachable = "1";
    set beresp.do_stream = true;   # Check memory usage it'll grow in fetch_chunksize blocks (128k by default) if
            # the backend doesn't send a Content-Length header, so only enable it for big objects
    set beresp.do_gzip = false;  # Don't try to compress it for storage
  }

  # Sometimes, a 301 or 302 redirect formed via Apache's mod_rewrite can mess with the HTTP port that is being passed along.
  # This often happens with simple rewrite rules in a scenario where Varnish runs on :80 and Apache on :8080 on the same box.
  # A redirect can then often redirect the end-user to a URL on :8080, where it should be :80.
  # This may need finetuning on your setup.
  #
  # To prevent accidental replace, we only filter the 301/302 redirects for now.
  if (beresp.status == 301 || beresp.status == 302) {
    set beresp.http.Location = regsub(beresp.http.Location, ":[0-9]+", "");
  }

  # Set 2min cache if unset for static files
  if (beresp.ttl <= 0s || beresp.http.Set-Cookie || beresp.http.Vary == "*") {
    set beresp.ttl = 120s; # Important, you shouldn't rely on this, SET YOUR HEADERS in the backend
    set beresp.uncacheable = true;
    return (deliver);
  }

  # Allow stale content, in case the backend goes down.
  # make Varnish keep all objects for 6 hours beyond their TTL
  set beresp.grace = 0s;

  return (deliver);
}

# The routine when we deliver the HTTP request to the user
# Last chance to modify headers that are sent to the client
sub vcl_deliver {
# Called before a cached object is delivered to the client.

  # Keep ban-lurker headers only if debugging is enabled
  if (!resp.http.X-Cache-Debug) {
      # Remove ban-lurker friendly custom headers when delivering to client
      unset resp.http.X-Url;
      unset resp.http.X-Host;
      unset resp.http.X-Cache-Tags;
  }
  # In Varnish 4 the obj.hits counter behaviour has changed, so we use a
  # different method: if X-Varnish contains only 1 id, we have a miss, if it
  # contains more (and therefore a space), we have a hit.
  if (resp.http.X-Cache-Debug) {
    if (resp.http.X-Varnish ~ " ") {
     	set resp.http.X-Cache = "HIT";
    } else {
    	set resp.http.X-Cache = "MISS";
    }
    #if (obj.hits > 0) { # Add debug header to see if it's a HIT/MISS and the number of hits, disable when not needed
    #    set resp.http.X-Cache = "HIT";
    #} else {
    #    set resp.http.X-Cache = "MISS";
    #}
  }

  # Please note that obj.hits behaviour changed in 4.0, now it counts per objecthead, not per object
  # and obj.hits may not be reset in some cases where bans are in use. See bug 1492 for details.
  # So take hits with a grain of salt
  set resp.http.X-Cache-Hits = obj.hits;

  # Remove some headers: PHP version
  unset resp.http.X-Powered-By;

  # Remove some headers: Apache version & OS
  unset resp.http.Server;
  unset resp.http.X-Drupal-Cache;
  unset resp.http.X-Varnish;
  unset resp.http.Via;
  unset resp.http.Link;

  set resp.http.Access-Control-Allow-Credentials = "true";
  set resp.http.Access-Control-Allow-Origin = "*";
  set resp.http.Access-Control-Expose-Headers = "*";
  set resp.http.Access-Control-Allow-Methods = "GET, POST, OPTIONS, PUT, DELETE";
  set resp.http.Access-Control-Allow-Headers = "accept, content-type, authorization, pragma, cache-control, AuthorizationFF";
  set resp.http.Access-Control-Max-Age = "1728000";

  return (deliver);
}

sub vcl_purge {
    # restart request
    #set req.http.X-Purge = "Yes";
    #return(restart);
}

sub vcl_synth {
  if (resp.status == 720) {
    # We use this special error status 720 to force redirects with 301 (permanent) redirects
    # To use this, call the following from anywhere in vcl_recv: error 720 "http://host/new.html"
    set resp.status = 301;
    set resp.http.Location = resp.reason;
    return (deliver);
  } elseif (resp.status == 721) {
    # And we use error status 721 to force redirects with a 302 (temporary) redirect
    # To use this, call the following from anywhere in vcl_recv: error 720 "http://host/new.html"
    set resp.status = 302;
    set resp.http.Location = resp.reason;
    return (deliver);
  } elseif (resp.status == 750) {
      set resp.status = 301;
      set resp.http.Location = "https://" + req.http.Host + req.url;
      return (deliver);
   }

  return (deliver);
}

sub vcl_fini {
# Called when VCL is discarded only after all requests have exited the VCL. Typically used to clean up VMODs.

  return (ok);
}

vcl 4.0;
## Based on: https://github.com/mattiasgeniar/varnish-4.0-configuration-templates/blob/master/default.vcl
# Corrected & improved for 4.0.2 by jnerin@gmail.com
import std;
import directors;

include "badbot.vcl";

backend alexa1 {
        .host = "192.168.16.3";
        .port = "8080";

        .max_connections = 300; # That's it
        .first_byte_timeout     = 300s;   # How long to wait before we receive a first byte from our backend?
        .connect_timeout        = 5s;     # How long to wait for a backend connection?
        .between_bytes_timeout  = 2s;     # How long to wait between bytes received from our backend?

#        .probe = {
#	       .request =
#                 "GET /robots.txt HTTP/1.1"
#                 "Host: admin.platform.bricks.20steps.de"
#                 "Connection: close";
#            .interval = 5s;
#            .timeout = 1s;
#            .window = 5;
#            .threshold = 3;
#        }
}

#backend alexa2 {
#        .host = "192.168.13.20";
#        .port = "8080";
#
#        .max_connections = 300; # That's it
#        .first_byte_timeout     = 300s;   # How long to wait before we receive a first byte from our backend?
#        .connect_timeout        = 5s;     # How long to wait for a backend connection?
#        .between_bytes_timeout  = 2s;     # How long to wait between bytes received from our backend?
#
#        .probe = {
#            .request =
#                 "GET /robots.txt HTTP/1.1"
#                 "Host: admin.platform.bricks.20steps.de"
#                 "Connection: close";
#            .interval = 5s;
#            .timeout = 1s;
#            .window = 5;
#            .threshold = 3;
#        }
#}

#backend alexaj1 {
#        .host = "192.168.14.17";
#        .port = "8080";
#
#        .max_connections = 300; # That's it
#        .first_byte_timeout     = 300s;   # How long to wait before we receive a first byte from our backend?
#        .connect_timeout        = 5s;     # How long to wait for a backend connection?
#        .between_bytes_timeout  = 2s;     # How long to wait between bytes received from our backend?
#
#        .probe = {
#            .request =
#                 "GET /robots.txt HTTP/1.1"
#                 "Host: admin.platform.bricks.20steps.de"
#                 "Connection: close";
#            .interval = 5s;
#            .timeout = 1s;
#            .window = 5;
#            .threshold = 3;
#        }
#}

acl invalidators {
  # ACL we'll use later to allow purges
  "localhost";
  "127.0.0.1";
  "::1";
  "192.168.0.0"/16;
}

sub vcl_init {
# Called when VCL is loaded, before any requests pass through it. Typically used to initialize VMODs.

  include "director.vcl";

}

sub vcl_recv {
# Called at the beginning of a request, after the complete request has been received and parsed. Its purpose is to decide whether or not to serve the request, how to do it, and, if applicable, which backend to use.
# also used to modify the request

  call badbot;

  ## REQUEST NORMALIZATION
  # Normalize the header, remove the port (in case you're testing this on various TCP ports)
  set req.http.Host = regsub(req.http.Host, ":[0-9]+", "");

  # Normalize the query arguments except for phpmyadmin
  if (req.http.Host ~ "phpmyadmin") {
     # no sorting here
  } else if (req.url !~ "wp-admin") {
    set req.url = std.querysort(req.url);
  }

  if (req.restarts == 0) {
    if (req.http.X-Forwarded-For) { # set or append the client.ip to X-Forwarded-For header
      set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip;
    } else {
      set req.http.X-Forwarded-For = client.ip;
    }
  }

  # For mod_pagespeed (cp. https://developers.google.com/speed/pagespeed/module/downstream-caching)
  # Tell PageSpeed not to use optimizations specific to this request.
  set req.http.PS-CapabilityList = "fully general optimizations only";

  # Don't allow external entities to force beaconing.
  unset req.http.PS-ShouldBeacon;

  ## letsencrypt
  if (req.url ~ "acme-challenge") {
     return (pipe);
  }

  ## http to https

  if (
     (req.http.Host ~ "alexa.20steps.de")
      && req.http.X_FORWARDED_PROTO !~ "(?i)https") {
    if (
      # req.url !~ "wp-admin"
      # && req.url !~ "wp-login"
      # && req.url !~ "secure"
      # && req.url !~ "well-known"
      req.url !~ "well-known"
      ) {
        return (synth(750, ""));
     }
  }

  ## HOST MODIFICATION AND BACKEND SELECTION

  # default backend director
  set req.backend_hint = zone.backend(); # send all traffic to the apache_director

  # direct access to backend depending on Header
  if (req.http.X-Server-Select == "alexa1") {
     set req.backend_hint = alexa1;
  } else if (req.http.X-Server-Select == "alexa2") {
     # set req.backend_hint = alexa2;
  } else if (req.http.X-Server-Select == "alexaj1") {
     #set req.backend_hint = alexaj1;
  }

  if (req.http.Host ~ "^job\.") {
     #set req.backend_hint = alexaj1;
  }

  # modify host and/or backend depending on url for further processing

  # Normalize Accept-Encoding header
  # straight from the manual: https://www.varnish-cache.org/docs/3.0/tutorial/vary.html
  # TODO: Test if it's still needed, Varnish 4 now does this by itself if http_gzip_support = on
  # https://www.varnish-cache.org/docs/trunk/users-guide/compression.html
  # https://www.varnish-cache.org/docs/trunk/phk/gzip.html
  if (req.http.Accept-Encoding) {
    if (req.url ~ "\.(avi|bmp|bz2|cssgz|doc|divx|eot|flv|gif|gz|ico|jpeg|jpg|jsgz|mp3|mp4|mpg|mpeg|mk[av]|mov|ogg|ogv|pdf|png|qt|rar|swf|tbz|woff|xz|zip|webp)$") {
      # No point in compressing these
      unset req.http.Accept-Encoding;
    } elsif (req.http.Accept-Encoding ~ "gzip") {
      set req.http.Accept-Encoding = "gzip";
    } elsif (req.http.Accept-Encoding ~ "deflate") {
      set req.http.Accept-Encoding = "deflate";
    } else {
      # unkown algorithm
      unset req.http.Accept-Encoding;
    }
  }

  ## HANDLING DEPENDING ON HTTP METHOD

  # Provide PURGE HTTP method
  if (req.method == "PURGE") {
    if (!client.ip ~ invalidators) { # purge is the ACL defined at the begining
      # Not from an allowed IP? Then die with an error.
      return (synth(405, "Not allowed"));
    }
    # If you got this stage (and didn't error out above), purge the cached result
    return (purge);
  }

  if (req.method == "BAN") {
     if (!client.ip ~ invalidators) {
         return (synth(405, "Not allowed"));
      }

      if (req.http.X-Cache-Tags) {
            ban("obj.http.X-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
            );
      } else {
	    ban("obj.http.X-Host ~ " + req.http.X-Host
                + " && obj.http.X-Url ~ " + req.http.X-Url
         	+ " && obj.http.content-type ~ " + req.http.X-Content-Type
      	    );
      }

      return (synth(200, "Banned"));
  }

  # Only deal with "normal" types
  if (req.method != "GET" &&
      req.method != "HEAD" &&
      req.method != "PUT" &&
      req.method != "POST" &&
      req.method != "TRACE" &&
      req.method != "OPTIONS" &&
      req.method != "PATCH" &&
      req.method != "DELETE") {
    /* Non-RFC2616 or CONNECT which is weird. */
    return (pipe);
  }

  # Implementing websocket support (https://www.varnish-cache.org/docs/4.0/users-guide/vcl-example-websockets.html)
  if (req.http.Upgrade ~ "(?i)websocket") {
    return (pipe);
  }

  # Only cache GET or HEAD requests. This makes sure the POST requests are always passed.
  if (req.method != "GET" && req.method != "HEAD") {
    return (pass);
  }

  ## COOKIE HANDLING

  # Some generic URL manipulation, useful for all templates that follow
  # First remove the Google Analytics added parameters, useless for our backend
  if (req.url ~ "(\?|&)(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=") {
    set req.url = regsuball(req.url, "&(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", "");
    set req.url = regsuball(req.url, "\?(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", "?");
    set req.url = regsub(req.url, "\?&", "?");
    set req.url = regsub(req.url, "\?$", "");
  }

  # Strip hash, server doesn't need it.
  if (req.url ~ "\#") {
    set req.url = regsub(req.url, "\#.*$", "");
  }

  # Strip a trailing ? if it exists
  if (req.url ~ "\?$") {
    set req.url = regsub(req.url, "\?$", "");
  }

  # Some generic cookie manipulation, useful for all templates that follow
  # Remove the "has_js" cookie
  set req.http.Cookie = regsuball(req.http.Cookie, "has_js=[^;]+(; )?", "");

  # Remove any Google Analytics based cookies
  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, "utmctr=[^;]+(; )?", "");
  set req.http.Cookie = regsuball(req.http.Cookie, "utmcmd.=[^;]+(; )?", "");
  set req.http.Cookie = regsuball(req.http.Cookie, "utmccn.=[^;]+(; )?", "");

  # Remove DoubleClick offensive cookies
  set req.http.Cookie = regsuball(req.http.Cookie, "__gads=[^;]+(; )?", "");

  # Remove the Quant Capital cookies (added by some plugin, all __qca)
  set req.http.Cookie = regsuball(req.http.Cookie, "__qc.=[^;]+(; )?", "");

  # Remove the AddThis cookies
  set req.http.Cookie = regsuball(req.http.Cookie, "__atuv.=[^;]+(; )?", "");

  # Remove a ";" prefix in the cookie if present
  set req.http.Cookie = regsuball(req.http.Cookie, "^;\s*", "");

  # Are there cookies left with only spaces or that are empty?
  if (req.http.cookie ~ "^\s*$") {
    unset req.http.cookie;
  }

  ## Allow for refreshing using <shift>+<reload> in browser
  if (req.http.Cache-Control ~ "(?i)no-cache" && client.ip ~ invalidators) {
    if (! (req.http.Via || req.http.User-Agent ~ "(?i)bot" || req.http.X-Purge)) {
      #set req.hash_always_miss = true; # Doesn't seems to refresh the object in the cache
      #return(purge); # Couple this with restart in vcl_purge and X-Purge header to avoid loops
    }
  }

  ## DO NOT CACHE APPLE APP SITE ASSOCIATIONS
  if (req.url ~ "apple-app-site-association") {
     return (pass);
  }

  ## DO NOT CACHE DEPENDING ON HOST/URL

  # do not cache depending on host/url
  if (
    req.url ~ "^/(wp-admin|admin)"
    || req.url ~ "(secure)"
    || req.url ~ "(raten|rechner|eAntrag)"
  ) {
      return (pass);
  }

  ## REMOVE COOKIES AS PREREQUESTITE FOR CACHING DEPENDING ON TYPE/URL/HOST

  # Remove all cookies for static files
  # A valid discussion could be held on this line: do you really need to cache static files that don't cause load? Only if you have memory left.
  # Sure, there's disk I/O, but chances are your OS will already have these files in their buffers (thus memory).
  # Before you blindly enable this, have a read here: http://mattiasgeniar.be/2012/11/28/stop-caching-static-files/
  if (req.url ~ "^[^?]*\.(avi|bmp|bz2|css|cssgz|doc|divx|eot|flv|gif|gz|ico|jpeg|jpg|js|jsgz|less|mp3|mp4|mpg|mpeg|mk[av]|mov|ogg|ogv|pdf|png|qt|rar|rtf|swf|txt|tbz|woff|xml|xz|zip|webp)$") {
    unset req.http.Cookie;
    return (hash);
  } else if (req.url ~ "timthumb.php") {
    unset req.http.Cookie;
    return (hash);
  }

  # Send Surrogate-Capability headers to announce ESI support to backend
  set req.http.Surrogate-Capability = "key=ESI/1.0";

  # allow caching even when using Authorization
  #if (req.http.Authorization) {
  #  # Not cacheable by default
  #  return (pass);
  #}

  #return (hash);
}

sub vcl_pipe {
# Called upon entering pipe mode. In this mode, the request is passed on to the backend, and any further data from both the client and backend is passed on unaltered until either end closes the connection. Basically, Varnish will degrade into a simple TCP proxy, shuffling bytes back and forth. For a connection in pipe mode, no other VCL subroutine will ever get called after vcl_pipe.

  # Note that only the first request to the backend will have
  # X-Forwarded-For set.  If you use X-Forwarded-For and want to
  # have it set for all requests, make sure to have:
  # set bereq.http.connection = "close";
  # here.  It is not set by default as it might break some broken web
  # applications, like IIS with NTLM authentication.

  #set bereq.http.Connection = "Close";

  # Implementing websocket support (https://www.varnish-cache.org/docs/4.0/users-guide/vcl-example-websockets.html)
       if (req.http.upgrade) {
          set bereq.http.upgrade = req.http.upgrade;
       }

  return (pipe);
}

sub vcl_pass {
# Called upon entering pass mode. In this mode, the request is passed on to the backend, and the backend's response is passed on to the client, but is not entered into the cache. Subsequent requests submitted over the same client connection are handled normally.

  # return (pass);
}

# The data on which the hashing will take place
sub vcl_hash {
# Called after vcl_recv to create a hash value for the request. This is used as a key to look up the object in Varnish.

  hash_data(req.url);

  if (req.http.host) {
    hash_data(req.http.host);
  } else {
    hash_data(server.ip);
  }

  # hash cookies for requests that have them
  if (req.http.Cookie) {
    hash_data(req.http.Cookie);
  }

}

sub vcl_hit {
  # Called when a cache lookup is successful.

  # mod_pagespeed
  # 5% of the time ignore that we got a cache hit and send the request to the
  # backend anyway for instrumentation.
  if (std.random(0, 100) < 5) {
    set req.http.PS-ShouldBeacon = "futuresteps25";
    return (pass);
  }

  if (obj.ttl >= 0s) {
    # A pure unadultered hit, deliver it
    return (deliver);
  }

  # https://www.varnish-cache.org/docs/trunk/users-guide/vcl-grace.html
  # When several clients are requesting the same page Varnish will send one request to the backend and place the others on hold while fetching one copy from the backend. In some products this is called request coalescing and Varnish does this automatically.
  # If you are serving thousands of hits per second the queue of waiting requests can get huge. There are two potential problems - one is a thundering herd problem - suddenly releasing a thousand threads to serve content might send the load sky high. Secondly - nobody likes to wait. To deal with this we can instruct Varnish to keep the objects in cache beyond their TTL and to serve the waiting requests somewhat stale content.

#  if (!std.healthy(req.backend_hint) && (obj.ttl + obj.grace > 0s)) {
#    return (deliver);
#  } else {
#    return (fetch);
#  }

  # We have no fresh fish. Lets look at the stale ones.
  if (std.healthy(req.backend_hint)) {
    # Backend is healthy. Limit age to 10s.
        if (obj.ttl + 10s > 0s) {
            #set req.http.grace = "normal(limited)";
            return (deliver);
        } else {
            # No candidate for grace. Fetch a fresh object.
	    return(fetch);
       }
  } else {
    # backend is sick - use full grace
        if (obj.ttl + obj.grace > 0s) {
            #set req.http.grace = "full";
      return (deliver);
    } else {
      # no graced object.
      return (fetch);
    }
  }

  # fetch & deliver once we get the result
  return (fetch);  # Dead code, keep as a safeguard
}

sub vcl_miss {
# Called after a cache lookup if the requested document was not found in the cache. Its purpose is to decide whether or not to attempt to retrieve the document from the backend, and which backend to use.

  # mod_pagespeed
  # Instrument 25% of cache misses.
  if (std.random(0, 100) < 25) {
    set req.http.PS-ShouldBeacon = "futuresteps25";
    return (pass);
  }

  return (fetch);
}

# Handle the HTTP request coming from our backend
sub vcl_backend_response {
# Called after the response headers has been successfully retrieved from the backend.

  # Set ban-lurker friendly custom headers
  set beresp.http.X-Url = bereq.url;
  set beresp.http.X-Host = bereq.http.host;

  if (beresp.status >= 400 || beresp.status >= 500) {
     set beresp.uncacheable = true;
     return (deliver);
  }

  # Pause ESI request and remove Surrogate-Control header
  if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
    unset beresp.http.Surrogate-Control;
    set beresp.do_esi = true;
  }

  # send json as text/html instead of application/json as MSIE wants to save the "file" otherwise
  if (bereq.url ~ "bricks/api") {
     # set beresp.http.Content-Type = "text/html; charset=utf-8";
  }

  # Enable cache for all static files
  # The same argument as the static caches from above: monitor your cache size, if you get data nuked out of it, consider giving up the static file cache.
  # Before you blindly enable this, have a read here: http://mattiasgeniar.be/2012/11/28/stop-caching-static-files/
  if (bereq.url ~ "^[^?]*\.(avi|bmp|bz2|css|cssgz|doc|divx|eot|flv|gif|gz|ico|jpeg|jpg|js|jsgz|less|mp3|mp4|mpg|mpeg|mk[av]|mov|ogg|ogv|pdf|png|qt|rar|rtf|swf|txt|tbz|woff|xml|xz|zip|webp)$") {
    set beresp.http.cache-control = "max-age=604800";
    unset beresp.http.set-cookie;
    unset beresp.http.expires;
    set beresp.http.cachable = "1";
  }

  # Large static files are delivered directly to the end-user without
  # waiting for Varnish to fully read the file first.
  # Varnish 4 fully supports Streaming, so use streaming here to avoid locking.
  if (bereq.url ~ "^[^?]*\.(mp[34]|rar|tar|tgz|gz|wav|zip|bz2|xz|7z|avi|mov|ogm|mpe?g|mk[av])(\?.*)?$") {
    unset beresp.http.set-cookie;
    set beresp.ttl = 3600s;
    unset beresp.http.expires;
    set beresp.http.cache-control = "max-age=3600";
    set beresp.http.cachable = "1";
    set beresp.do_stream = true;   # Check memory usage it'll grow in fetch_chunksize blocks (128k by default) if
            # the backend doesn't send a Content-Length header, so only enable it for big objects
    set beresp.do_gzip = false;  # Don't try to compress it for storage
  }

  # Sometimes, a 301 or 302 redirect formed via Apache's mod_rewrite can mess with the HTTP port that is being passed along.
  # This often happens with simple rewrite rules in a scenario where Varnish runs on :80 and Apache on :8080 on the same box.
  # A redirect can then often redirect the end-user to a URL on :8080, where it should be :80.
  # This may need finetuning on your setup.
  #
  # To prevent accidental replace, we only filter the 301/302 redirects for now.
  if (beresp.status == 301 || beresp.status == 302) {
    set beresp.http.Location = regsub(beresp.http.Location, ":[0-9]+", "");
  }

  # Set 2min cache if unset for static files
  if (beresp.ttl <= 0s || beresp.http.Set-Cookie || beresp.http.Vary == "*") {
    set beresp.ttl = 120s; # Important, you shouldn't rely on this, SET YOUR HEADERS in the backend
    set beresp.uncacheable = true;
    return (deliver);
  }

  # Allow stale content, in case the backend goes down.
  # make Varnish keep all objects for 6 hours beyond their TTL
  set beresp.grace = 0s;

  return (deliver);
}

# The routine when we deliver the HTTP request to the user
# Last chance to modify headers that are sent to the client
sub vcl_deliver {
# Called before a cached object is delivered to the client.

  # Keep ban-lurker headers only if debugging is enabled
  if (!resp.http.X-Cache-Debug) {
      # Remove ban-lurker friendly custom headers when delivering to client
      unset resp.http.X-Url;
      unset resp.http.X-Host;
      unset resp.http.X-Cache-Tags;
  }
  # In Varnish 4 the obj.hits counter behaviour has changed, so we use a
  # different method: if X-Varnish contains only 1 id, we have a miss, if it
  # contains more (and therefore a space), we have a hit.
  if (resp.http.X-Cache-Debug) {
    if (resp.http.X-Varnish ~ " ") {
     	set resp.http.X-Cache = "HIT";
    } else {
    	set resp.http.X-Cache = "MISS";
    }
    #if (obj.hits > 0) { # Add debug header to see if it's a HIT/MISS and the number of hits, disable when not needed
    #    set resp.http.X-Cache = "HIT";
    #} else {
    #    set resp.http.X-Cache = "MISS";
    #}
  }

  # Please note that obj.hits behaviour changed in 4.0, now it counts per objecthead, not per object
  # and obj.hits may not be reset in some cases where bans are in use. See bug 1492 for details.
  # So take hits with a grain of salt
  set resp.http.X-Cache-Hits = obj.hits;

  # Remove some headers: PHP version
  unset resp.http.X-Powered-By;

  # Remove some headers: Apache version & OS
  unset resp.http.Server;
  unset resp.http.X-Drupal-Cache;
  unset resp.http.X-Varnish;
  unset resp.http.Via;
  unset resp.http.Link;

  set resp.http.Access-Control-Allow-Credentials = "true";
  set resp.http.Access-Control-Allow-Origin = "*";
  set resp.http.Access-Control-Expose-Headers = "*";
  set resp.http.Access-Control-Allow-Methods = "GET, POST, OPTIONS, PUT, DELETE";
  set resp.http.Access-Control-Allow-Headers = "accept, content-type, authorization, pragma, cache-control, AuthorizationFF";
  set resp.http.Access-Control-Max-Age = "1728000";

  return (deliver);
}

sub vcl_purge {
    # restart request
    #set req.http.X-Purge = "Yes";
    #return(restart);
}

sub vcl_synth {
  if (resp.status == 720) {
    # We use this special error status 720 to force redirects with 301 (permanent) redirects
    # To use this, call the following from anywhere in vcl_recv: error 720 "http://host/new.html"
    set resp.status = 301;
    set resp.http.Location = resp.reason;
    return (deliver);
  } elseif (resp.status == 721) {
    # And we use error status 721 to force redirects with a 302 (temporary) redirect
    # To use this, call the following from anywhere in vcl_recv: error 720 "http://host/new.html"
    set resp.status = 302;
    set resp.http.Location = resp.reason;
    return (deliver);
  } elseif (resp.status == 750) {
      set resp.status = 301;
      set resp.http.Location = "https://" + req.http.Host + req.url;
      return (deliver);
   }

  return (deliver);
}

sub vcl_fini {
# Called when VCL is discarded only after all requests have exited the VCL. Typically used to clean up VMODs.

  return (ok);
}


84 - un-amazeeio-ify some more

84 - un-amazeeio-ify some more


Update the XHTML in the default guru page.

Submitted by:	mattrobenolt
Approved by:	mithradir

/*-
 * Copyright (c) 2006 Verdens Gang AS
 * Copyright (c) 2006-2014 Varnish Software AS
 * All rights reserved.
 *
 * Author: Poul-Henning Kamp <phk@phk.freebsd.dk>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *

 *
 * The built-in (previously called default) VCL code.
 *
 * NB! You do NOT need to copy & paste all of these functions into your
 * own vcl code, if you do not provide a definition of one of these
 * functions, the compiler will automatically fall back to the default
 * code from this file.
 *
 * This code will be prefixed with a backend declaration built from the
 * -b argument.
 */

vcl 4.0;

#######################################################################
# Client side

sub vcl_recv {
    if (req.method == "PRI") {
	/* We do not support SPDY or HTTP/2.0 */
	return (synth(405));
    }
    if (req.method != "GET" &&
      req.method != "HEAD" &&
      req.method != "PUT" &&
      req.method != "POST" &&
      req.method != "TRACE" &&
      req.method != "OPTIONS" &&
      req.method != "DELETE") {
        /* Non-RFC2616 or CONNECT which is weird. */
        return (pipe);
    }

    if (req.method != "GET" && req.method != "HEAD") {
        /* We only deal with GET and HEAD by default */
        return (pass);
    }
    if (req.http.Authorization || req.http.Cookie) {
        /* Not cacheable by default */
        return (pass);
    }
    return (hash);
}

sub vcl_pipe {
    # By default Connection: close is set on all piped requests, to stop
    # connection reuse from sending future requests directly to the
    # (potentially) wrong backend. If you do want this to happen, you can undo
    # it here.
    # unset bereq.http.connection;
    return (pipe);
}

sub vcl_pass {
    return (fetch);
}

sub vcl_hash {
    hash_data(req.url);
    if (req.http.host) {
        hash_data(req.http.host);
    } else {
        hash_data(server.ip);
    }
    return (lookup);
}

sub vcl_purge {
    return (synth(200, "Purged"));
}

sub vcl_hit {
    if (obj.ttl >= 0s) {
        // A pure unadultered hit, deliver it
        return (deliver);
    }
    if (obj.ttl + obj.grace > 0s) {
        // Object is in grace, deliver it
        // Automatically triggers a background fetch
        return (deliver);
    }
    // fetch & deliver once we get the result
    return (fetch);
}

sub vcl_miss {
    return (fetch);
}

sub vcl_deliver {
    return (deliver);
}

/*
 * We can come here "invisibly" with the following errors: 413, 417 & 503
 */
sub vcl_synth {
    set resp.http.Content-Type = "text/html; charset=utf-8";
    set resp.http.Retry-After = "5";
    synthetic( {"<!DOCTYPE html>

    <title>"} + resp.status + " " + resp.reason + {"</title>

Error "} + resp.status + " " + resp.reason + {"

"} + resp.reason + {"

Guru Meditation:

XID: "} + req.xid + {"

Varnish cache server

"} );
    return (deliver);
}

#######################################################################
# Backend Fetch

sub vcl_backend_fetch {
    return (fetch);
}

sub vcl_backend_response {
    if (beresp.ttl <= 0s ||
      beresp.http.Set-Cookie ||
      beresp.http.Surrogate-control ~ "no-store" ||
      (!beresp.http.Surrogate-Control &&
        beresp.http.Cache-Control ~ "no-cache|no-store|private") ||
      beresp.http.Vary == "*") {
        /*
        * Mark as "Hit-For-Pass" for the next 2 minutes
        */
        set beresp.ttl = 120s;
        set beresp.uncacheable = true;
    }
    return (deliver);
}

sub vcl_backend_error {
    set beresp.http.Content-Type = "text/html; charset=utf-8";
    set beresp.http.Retry-After = "5";
    synthetic( {"<!DOCTYPE html>

    <title>"} + beresp.status + " " + beresp.reason + {"</title>

Error "} + beresp.status + " " + beresp.reason + {"

"} + beresp.reason + {"

Guru Meditation:

XID: "} + bereq.xid + {"

Varnish cache server

"} );
    return (deliver);
}

#######################################################################
# Housekeeping

sub vcl_init {
    return (ok);
}

sub vcl_fini {
    return (ok);
}

/*-
 * Copyright (c) 2006 Verdens Gang AS
 * Copyright (c) 2006-2014 Varnish Software AS
 * All rights reserved.
 *
 * Author: Poul-Henning Kamp <phk@phk.freebsd.dk>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *

 *
 * The built-in (previously called default) VCL code.
 *
 * NB! You do NOT need to copy & paste all of these functions into your
 * own vcl code, if you do not provide a definition of one of these
 * functions, the compiler will automatically fall back to the default
 * code from this file.
 *
 * This code will be prefixed with a backend declaration built from the
 * -b argument.
 */

vcl 4.0;

#######################################################################
# Client side

sub vcl_recv {
    if (req.method == "PRI") {
	/* We do not support SPDY or HTTP/2.0 */
	return (synth(405));
    }
    if (req.method != "GET" &&
      req.method != "HEAD" &&
      req.method != "PUT" &&
      req.method != "POST" &&
      req.method != "TRACE" &&
      req.method != "OPTIONS" &&
      req.method != "DELETE") {
        /* Non-RFC2616 or CONNECT which is weird. */
        return (pipe);
    }

    if (req.method != "GET" && req.method != "HEAD") {
        /* We only deal with GET and HEAD by default */
        return (pass);
    }
    if (req.http.Authorization || req.http.Cookie) {
        /* Not cacheable by default */
        return (pass);
    }
    return (hash);
}

sub vcl_pipe {
    # By default Connection: close is set on all piped requests, to stop
    # connection reuse from sending future requests directly to the
    # (potentially) wrong backend. If you do want this to happen, you can undo
    # it here.
    # unset bereq.http.connection;
    return (pipe);
}

sub vcl_pass {
    return (fetch);
}

sub vcl_hash {
    hash_data(req.url);
    if (req.http.host) {
        hash_data(req.http.host);
    } else {
        hash_data(server.ip);
    }
    return (lookup);
}

sub vcl_purge {
    return (synth(200, "Purged"));
}

sub vcl_hit {
    if (obj.ttl >= 0s) {
        // A pure unadultered hit, deliver it
        return (deliver);
    }
    if (obj.ttl + obj.grace > 0s) {
        // Object is in grace, deliver it
        // Automatically triggers a background fetch
        return (deliver);
    }
    // fetch & deliver once we get the result
    return (fetch);
}

sub vcl_miss {
    return (fetch);
}

sub vcl_deliver {
    return (deliver);
}

/*
 * We can come here "invisibly" with the following errors: 413, 417 & 503
 */
sub vcl_synth {
    set resp.http.Content-Type = "text/html; charset=utf-8";
    set resp.http.Retry-After = "5";
    synthetic( {"
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

    <title>"} + resp.status + " " + resp.reason + {"</title>

Error "} + resp.status + " " + resp.reason + {"

"} + resp.reason + {"

Guru Meditation:

XID: "} + req.xid + {"

Varnish cache server

"} );
    return (deliver);
}

#######################################################################
# Backend Fetch

sub vcl_backend_fetch {
    return (fetch);
}

sub vcl_backend_response {
    if (beresp.ttl <= 0s ||
      beresp.http.Set-Cookie ||
      beresp.http.Surrogate-control ~ "no-store" ||
      (!beresp.http.Surrogate-Control &&
        beresp.http.Cache-Control ~ "no-cache|no-store|private") ||
      beresp.http.Vary == "*") {
        /*
        * Mark as "Hit-For-Pass" for the next 2 minutes
        */
        set beresp.ttl = 120s;
        set beresp.uncacheable = true;
    }
    return (deliver);
}

sub vcl_backend_error {
    set beresp.http.Content-Type = "text/html; charset=utf-8";
    set beresp.http.Retry-After = "5";
    synthetic( {"
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

    <title>"} + beresp.status + " " + beresp.reason + {"</title>

Error "} + beresp.status + " " + beresp.reason + {"

"} + beresp.reason + {"

Guru Meditation:

XID: "} + bereq.xid + {"

Varnish cache server

"} );
    return (deliver);
}

#######################################################################
# Housekeeping

sub vcl_init {
    return (ok);
}

sub vcl_fini {
    return (ok);
}


Update main.vcl

Update main.vcl

import querystring;

sub sort_comma_separated_value {
	# This function takes a CSV and tranforms it into a url where each
	# comma-separated-value is a query-string parameter and then uses
	# Fastly's querystring.sort function to sort the values. Once sorted
	# it then turn the query-parameters back into a CSV.
	# Set the CSV on the header `Sort-Value`.
	# Returns the sorted CSV on the header `Sorted-Value`.
	declare local var.value STRING;
	set var.value = req.http.Sort-value;

	# If query value does not exist or is empty, set it to ""
	set var.value = if(var.value != "", var.value, "");

	# Replace all `&` characters with `^`, this is because `&` would break the value up into pieces.
	set var.value = regsuball(var.value, "&", "^");

	# Replace all `,` characters with `&` to break them into individual query values
	# Append `1-` infront of all the query values to make them simpler to transform later
	set var.value = "1-" regsuball(var.value, ",", "&1-");

	# Create a querystring-like string in order for querystring.sort to work.
	set var.value = querystring.sort("?" var.value);

	# Grab all the query values from the sorted url
	set var.value = regsub(var.value, "\?", "");

	# Reverse all the previous transformations to get back the single `features` query value value
	set var.value = regsuball(var.value, "1-", "");
	set var.value = regsuball(var.value, "&", ",");
	set var.value = regsuball(var.value, "\^", "&");

	set req.http.Sorted-Value = var.value;
}

sub normalise_querystring_parameters_for_polyfill_bundle {
	# Store the url without the querystring into a temporary header.
	declare local var.url STRING;
	set var.url = querystring.remove(req.url);

	declare local var.querystring STRING;
	set var.querystring = "?";

	# Remove all querystring parameters which are not part of the public API.
	# set req.url = querystring.regfilter_except(req.url, "^(features|excludes|rum|unknown|flags|version|ua|callback|compression)$");

	# (?i) makes the regex case-insensitive
	# The regex will match only if their are characters after `features=` which are not an ampersand (&).
	if (req.url.qs ~ "(?i)[^&=]*features=([^&]+)") {
		# Parameter has already been set, use the already set value.
		# re.group.1 is the first regex capture group in the regex above.
		if (std.strlen(re.group.1) < 100) {
			# We add the value of the features parameter to this header
			# This is to be able to have sort_comma_separated_value sort the value
			set req.http.Sort-Value = urldecode(re.group.1);
			call sort_comma_separated_value;
			# The header Sorted-Value now contains the sorted version of the features parameter.
			set var.querystring = querystring.set(var.querystring, "features", req.http.Sorted-Value);
		} else {
			set var.querystring = querystring.set(var.querystring, "features", urldecode(re.group.1));
		}
	} else {
		# Parameter has not been set, use the default value.
		set var.querystring = querystring.set(var.querystring, "features", "default");
	}

	# (?i) makes the regex case-insensitive
	# The regex will match only if their are characters after `excludes=` which are not an ampersand (&).
	if (req.url.qs ~ "(?i)[^&=]*excludes=([^&]+)") {
		# Parameter has already been set, use the already set value.
		# re.group.1 is the first regex capture group in the regex above.
		if (std.strlen(re.group.1) < 100) {
			# We add the value of the excludes parameter to this header
			# This is to be able to have sort_comma_separated_value sort the value
			set req.http.Sort-Value = urldecode(re.group.1);
			call sort_comma_separated_value;
			# The header Sorted-Value now contains the sorted version of the excludes parameter.
			set var.querystring = querystring.set(var.querystring, "excludes", req.http.Sorted-Value);
		} else {
			set var.querystring = querystring.set(var.querystring, "excludes", urldecode(re.group.1));
		}
	} else {
		# If excludes is not set, set to default value ""
		set var.querystring = var.querystring "&excludes=";
	}

	# If rum is not set, set to default value "0"
	if (req.url.qs !~ "(?i)[^&=]*rum=([^&]+)") {
		set var.querystring = querystring.set(var.querystring, "rum", "0");
	} else {
		set var.querystring = querystring.set(var.querystring, "rum", re.group.1);
	}

	# If unknown is not set, set to default value "polyfill"
	if (req.url.qs !~ "(?i)[^&=]*unknown=([^&]+)") {
		set var.querystring = querystring.set(var.querystring, "unknown", "polyfill");
	} else {
		set var.querystring = querystring.set(var.querystring, "unknown", re.group.1);
	}

	# If flags is not set, set to default value ""
	if (req.url.qs !~ "(?i)[^&=]*flags=([^&]+)") {
		set var.querystring = var.querystring "&flags=";
	} else {
		set var.querystring = querystring.set(var.querystring, "flags", urldecode(re.group.1));
	}

	# If version is not set, set to default value ""
	declare local var.version STRING;
	if (req.url.qs !~ "(?i)[^&=]*version=([^&]+)") {
		set var.querystring = var.querystring "&version=";
	} else {
		if (re.group.1 == "3.111.0" || re.group.1 == "3.110.1" || re.group.1 == "3.109.0" || re.group.1 == "3.108.0" || re.group.1 == "3.104.0" || re.group.1 == "3.103.0" || re.group.1 == "3.101.0" || re.group.1 == "3.98.0" || re.group.1 == "3.96.0" || re.group.1 == "3.89.4" || re.group.1 == "3.53.1" || re.group.1 == "3.52.3" || re.group.1 == "3.52.2" || re.group.1 == "3.52.1" || re.group.1 == "3.52.0" || re.group.1 == "3.51.0" || re.group.1 == "3.50.2" || re.group.1 == "3.48.0" || re.group.1 == "3.46.0" || re.group.1 == "3.42.0" || re.group.1 == "3.41.0" || re.group.1 == "3.40.0" || re.group.1 == "3.39.0" || re.group.1 == "3.34.0" || re.group.1 == "3.27.4" || re.group.1 == "3.25.1") {
			set var.querystring = querystring.set(var.querystring, "version", re.group.1);
		} else {
			set var.querystring = var.querystring "&version=";
		}
	}

	# If ua is not set, normalise the User-Agent header based upon the version of the polyfill-library that has been requested.
	if (req.url.qs !~ "(?i)[^&=]*ua=([^&]+)") {
		if (req.url.qs ~ "(?i)[^&=]*version=3\.25\.1(&|$)") {
			call normalise_user_agent_3_25_1;
		} else {
			call normalise_user_agent_1_10_2;
		}
		set var.querystring = querystring.set(var.querystring, "ua", req.http.Normalized-User-Agent);
	} else {
		set var.querystring = querystring.set(var.querystring, "ua", urldecode(re.group.1));
	}

	# If callback is not set, set to default value ""
	if (req.url.qs !~ "(?i)[^&=]*callback=([^&]+)") {
		set var.querystring = var.querystring "&callback=";
	} else {
		set var.querystring = querystring.set(var.querystring, "callback", re.group.1);
	}

	# If compression is not set, use the best compression that the user-agent supports.
	if (req.url.qs !~ "(?i)[^&=]*compression=([^&]+)") {
		# When Fastly adds Brotli into the Accept-Encoding normalisation we can replace this with:
		# `set var.querystring = querystring.set(var.querystring, "compression", req.http.Accept-Encoding || "")`

		# Before SP2, IE/6 doesn't always read and cache gzipped content correctly.
		if (req.http.Fastly-Orig-Accept-Encoding && req.http.User-Agent !~ "MSIE 6") {
			if (req.http.Fastly-Orig-Accept-Encoding ~ "br") {
				set var.querystring = querystring.set(var.querystring, "compression", "br");
			} elsif (req.http.Fastly-Orig-Accept-Encoding ~ "gzip") {
				set var.querystring = querystring.set(var.querystring, "compression", "gzip");
			} else {
				set var.querystring = var.querystring "&compression=";
			}
		} else {
			set var.querystring = var.querystring "&compression=";
		}
	} else {
		set var.querystring = querystring.set(var.querystring, "compression", re.group.1);
	}
	set req.url = var.url var.querystring;
}

include "top_pops.vcl";
include "normalise-user-agent-3-25-1.vcl";
include "normalise-user-agent.vcl";

# The Fastly VCL boilerplate.
include "fastly-boilerplate-begin.vcl";

include "breadcrumbs.vcl";

sub vcl_recv {
	if (req.http.Fastly-Debug) {
		call breadcrumb_recv;
	}
}

sub vcl_recv {
	# js-compute-runtime currently can not handle a uri which is bigger than 1024
	# this has been fixed in js-compute-runtime but not yet released -- https://github.com/fastly/js-compute-runtime/commit/f36dee28789b1187652874fe76d1801c9a8e5974
	# when the fix is released and added into @fastly/js-compute we can remove this if statement
	if (std.strlen(req.url) < 900) {
		# We use this edge dictionary item as a flag for whether to use compute-at-edge for any requests.
		# If the item is not found, then we default to `"0"`, which we interpret to mean the flag is off.
		declare local var.compute-at-edge-active BOOL;
		if (std.atoi(table.lookup(compute_at_edge_config, "active", "0")) == 1) {
			set var.compute-at-edge-active = true;
		} else {
			set var.compute-at-edge-active = false;
		}
		if (var.compute-at-edge-active) {
			# if the querystring parameter `use-compute-at-edge-backend` is set to yes`
			# then use the c@e backend.
			# The querystring parameter is used within our tests, so that we can confirm
			# the compute-at-edge backend is working as we expect it to.
			declare local var.use-compute-at-edge-backend STRING;
			set var.use-compute-at-edge-backend = querystring.get(req.url, "use-compute-at-edge-backend");
			if (var.use-compute-at-edge-backend == "yes") {
				set req.backend = F_compute_at_edge;
				if (req.backend.healthy) {
					# if using the c@e backend then do not use the cache at all
					return(pass);
				}
			# requests can set the querystring parameter to `no` to avoid ever user compute-at-edge
			# if the querystring parameter is not set to no, then we run some logic to decide whether
			# to use compute-at-edge for this request or not.
			} else if (var.use-compute-at-edge-backend != "no") {
				declare local var.selected BOOL;
				set var.selected = randombool(1, std.atoi(table.lookup(compute_at_edge_config, "sample", "1000")));
				# this logic is saying, only use compute-at-edge for a request which has come from outside this Fastly
				# service. I.E. The request has directly from a client.
				# We also have logic which decides how many of those direct client requests use compute-at-edge.
				# We default to 1 in 1000 requests using compute-at-edge, this can be configured by the edge dictionary
				# named `compute_at_edge_config` and the item in the dictionary named `sample`
				if (var.selected && fastly_info.edge.is_tls && !req.is_background_fetch && !req.is_purge && req.restarts == 0 && fastly.ff.visits_this_service == 0) {
					set req.backend = F_compute_at_edge;
					if (req.backend.healthy) {
						# if using the c@e backend then do not use the cache at all
						return(pass);
					}
				}
			}
		}
	}
}

sub vcl_fetch {
	# if using the compute-at-edge backend, we want to ensure request collapsing is not enabled
	# we don't want request collapsing because request collapsing would cause multiple requests
	# for the same path to be served the same response, which would end up meaning one browser
	# would get a polyfill bundle meant for a different browser
	if (req.backend == F_compute_at_edge) {
		return(pass);
	}
}

include "redirects.vcl";
include "synthetic-responses.vcl";
include "polyfill-service.vcl";

# Finally include the last bit of VCL, this _must_ be last!
include "fastly-boilerplate-end.vcl";

import querystring;

sub sort_comma_separated_value {
	# This function takes a CSV and tranforms it into a url where each
	# comma-separated-value is a query-string parameter and then uses
	# Fastly's querystring.sort function to sort the values. Once sorted
	# it then turn the query-parameters back into a CSV.
	# Set the CSV on the header `Sort-Value`.
	# Returns the sorted CSV on the header `Sorted-Value`.
	declare local var.value STRING;
	set var.value = req.http.Sort-value;

	# If query value does not exist or is empty, set it to ""
	set var.value = if(var.value != "", var.value, "");

	# Replace all `&` characters with `^`, this is because `&` would break the value up into pieces.
	set var.value = regsuball(var.value, "&", "^");

	# Replace all `,` characters with `&` to break them into individual query values
	# Append `1-` infront of all the query values to make them simpler to transform later
	set var.value = "1-" regsuball(var.value, ",", "&1-");

	# Create a querystring-like string in order for querystring.sort to work.
	set var.value = querystring.sort("?" var.value);

	# Grab all the query values from the sorted url
	set var.value = regsub(var.value, "\?", "");

	# Reverse all the previous transformations to get back the single `features` query value value
	set var.value = regsuball(var.value, "1-", "");
	set var.value = regsuball(var.value, "&", ",");
	set var.value = regsuball(var.value, "\^", "&");

	set req.http.Sorted-Value = var.value;
}

sub normalise_querystring_parameters_for_polyfill_bundle {
	# Store the url without the querystring into a temporary header.
	declare local var.url STRING;
	set var.url = querystring.remove(req.url);

	declare local var.querystring STRING;
	set var.querystring = "?";

	# Remove all querystring parameters which are not part of the public API.
	# set req.url = querystring.regfilter_except(req.url, "^(features|excludes|rum|unknown|flags|version|ua|callback|compression)$");

	# (?i) makes the regex case-insensitive
	# The regex will match only if their are characters after `features=` which are not an ampersand (&).
	if (req.url.qs ~ "(?i)[^&=]*features=([^&]+)") {
		# Parameter has already been set, use the already set value.
		# re.group.1 is the first regex capture group in the regex above.
		if (std.strlen(re.group.1) < 100) {
			# We add the value of the features parameter to this header
			# This is to be able to have sort_comma_separated_value sort the value
			set req.http.Sort-Value = urldecode(re.group.1);
			call sort_comma_separated_value;
			# The header Sorted-Value now contains the sorted version of the features parameter.
			set var.querystring = querystring.set(var.querystring, "features", req.http.Sorted-Value);
		} else {
			set var.querystring = querystring.set(var.querystring, "features", urldecode(re.group.1));
		}
	} else {
		# Parameter has not been set, use the default value.
		set var.querystring = querystring.set(var.querystring, "features", "default");
	}

	# (?i) makes the regex case-insensitive
	# The regex will match only if their are characters after `excludes=` which are not an ampersand (&).
	if (req.url.qs ~ "(?i)[^&=]*excludes=([^&]+)") {
		# Parameter has already been set, use the already set value.
		# re.group.1 is the first regex capture group in the regex above.
		if (std.strlen(re.group.1) < 100) {
			# We add the value of the excludes parameter to this header
			# This is to be able to have sort_comma_separated_value sort the value
			set req.http.Sort-Value = urldecode(re.group.1);
			call sort_comma_separated_value;
			# The header Sorted-Value now contains the sorted version of the excludes parameter.
			set var.querystring = querystring.set(var.querystring, "excludes", req.http.Sorted-Value);
		} else {
			set var.querystring = querystring.set(var.querystring, "excludes", urldecode(re.group.1));
		}
	} else {
		# If excludes is not set, set to default value ""
		set var.querystring = var.querystring "&excludes=";
	}

	# If rum is not set, set to default value "0"
	if (req.url.qs !~ "(?i)[^&=]*rum=([^&]+)") {
		set var.querystring = querystring.set(var.querystring, "rum", "0");
	} else {
		set var.querystring = querystring.set(var.querystring, "rum", re.group.1);
	}

	# If unknown is not set, set to default value "polyfill"
	if (req.url.qs !~ "(?i)[^&=]*unknown=([^&]+)") {
		set var.querystring = querystring.set(var.querystring, "unknown", "polyfill");
	} else {
		set var.querystring = querystring.set(var.querystring, "unknown", re.group.1);
	}

	# If flags is not set, set to default value ""
	if (req.url.qs !~ "(?i)[^&=]*flags=([^&]+)") {
		set var.querystring = var.querystring "&flags=";
	} else {
		set var.querystring = querystring.set(var.querystring, "flags", urldecode(re.group.1));
	}

	# If version is not set, set to default value ""
	declare local var.version STRING;
	if (req.url.qs !~ "(?i)[^&=]*version=([^&]+)") {
		set var.querystring = var.querystring "&version=";
	} else {
		if (re.group.1 == "3.111.0" || re.group.1 == "3.110.1" || re.group.1 == "3.109.0" || re.group.1 == "3.108.0" || re.group.1 == "3.104.0" || re.group.1 == "3.103.0" || re.group.1 == "3.101.0" || re.group.1 == "3.98.0" || re.group.1 == "3.96.0" || re.group.1 == "3.89.4" || re.group.1 == "3.53.1" || re.group.1 == "3.52.3" || re.group.1 == "3.52.2" || re.group.1 == "3.52.1" || re.group.1 == "3.52.0" || re.group.1 == "3.51.0" || re.group.1 == "3.50.2" || re.group.1 == "3.48.0" || re.group.1 == "3.46.0" || re.group.1 == "3.42.0" || re.group.1 == "3.41.0" || re.group.1 == "3.40.0" || re.group.1 == "3.39.0" || re.group.1 == "3.34.0" || re.group.1 == "3.27.4" || re.group.1 == "3.25.1") {
			set var.querystring = querystring.set(var.querystring, "version", re.group.1);
		} else {
			set var.querystring = var.querystring "&version=";
		}
	}

	# If ua is not set, normalise the User-Agent header based upon the version of the polyfill-library that has been requested.
	if (req.url.qs !~ "(?i)[^&=]*ua=([^&]+)") {
		if (req.url.qs ~ "(?i)[^&=]*version=3\.25\.1(&|$)") {
			call normalise_user_agent_3_25_1;
		} else {
			call normalise_user_agent_1_10_1;
		}
		set var.querystring = querystring.set(var.querystring, "ua", req.http.Normalized-User-Agent);
	} else {
		set var.querystring = querystring.set(var.querystring, "ua", urldecode(re.group.1));
	}

	# If callback is not set, set to default value ""
	if (req.url.qs !~ "(?i)[^&=]*callback=([^&]+)") {
		set var.querystring = var.querystring "&callback=";
	} else {
		set var.querystring = querystring.set(var.querystring, "callback", re.group.1);
	}

	# If compression is not set, use the best compression that the user-agent supports.
	if (req.url.qs !~ "(?i)[^&=]*compression=([^&]+)") {
		# When Fastly adds Brotli into the Accept-Encoding normalisation we can replace this with:
		# `set var.querystring = querystring.set(var.querystring, "compression", req.http.Accept-Encoding || "")`

		# Before SP2, IE/6 doesn't always read and cache gzipped content correctly.
		if (req.http.Fastly-Orig-Accept-Encoding && req.http.User-Agent !~ "MSIE 6") {
			if (req.http.Fastly-Orig-Accept-Encoding ~ "br") {
				set var.querystring = querystring.set(var.querystring, "compression", "br");
			} elsif (req.http.Fastly-Orig-Accept-Encoding ~ "gzip") {
				set var.querystring = querystring.set(var.querystring, "compression", "gzip");
			} else {
				set var.querystring = var.querystring "&compression=";
			}
		} else {
			set var.querystring = var.querystring "&compression=";
		}
	} else {
		set var.querystring = querystring.set(var.querystring, "compression", re.group.1);
	}
	set req.url = var.url var.querystring;
}

include "top_pops.vcl";
include "normalise-user-agent-3-25-1.vcl";
include "normalise-user-agent.vcl";

# The Fastly VCL boilerplate.
include "fastly-boilerplate-begin.vcl";

include "breadcrumbs.vcl";

sub vcl_recv {
	if (req.http.Fastly-Debug) {
		call breadcrumb_recv;
	}
}

sub vcl_recv {
	# js-compute-runtime currently can not handle a uri which is bigger than 1024
	# this has been fixed in js-compute-runtime but not yet released -- https://github.com/fastly/js-compute-runtime/commit/f36dee28789b1187652874fe76d1801c9a8e5974
	# when the fix is released and added into @fastly/js-compute we can remove this if statement
	if (std.strlen(req.url) < 900) {
		# We use this edge dictionary item as a flag for whether to use compute-at-edge for any requests.
		# If the item is not found, then we default to `"0"`, which we interpret to mean the flag is off.
		declare local var.compute-at-edge-active BOOL;
		if (std.atoi(table.lookup(compute_at_edge_config, "active", "0")) == 1) {
			set var.compute-at-edge-active = true;
		} else {
			set var.compute-at-edge-active = false;
		}
		if (var.compute-at-edge-active) {
			# if the querystring parameter `use-compute-at-edge-backend` is set to yes`
			# then use the c@e backend.
			# The querystring parameter is used within our tests, so that we can confirm
			# the compute-at-edge backend is working as we expect it to.
			declare local var.use-compute-at-edge-backend STRING;
			set var.use-compute-at-edge-backend = querystring.get(req.url, "use-compute-at-edge-backend");
			if (var.use-compute-at-edge-backend == "yes") {
				set req.backend = F_compute_at_edge;
				if (req.backend.healthy) {
					# if using the c@e backend then do not use the cache at all
					return(pass);
				}
			# requests can set the querystring parameter to `no` to avoid ever user compute-at-edge
			# if the querystring parameter is not set to no, then we run some logic to decide whether
			# to use compute-at-edge for this request or not.
			} else if (var.use-compute-at-edge-backend != "no") {
				declare local var.selected BOOL;
				set var.selected = randombool(1, std.atoi(table.lookup(compute_at_edge_config, "sample", "1000")));
				# this logic is saying, only use compute-at-edge for a request which has come from outside this Fastly
				# service. I.E. The request has directly from a client.
				# We also have logic which decides how many of those direct client requests use compute-at-edge.
				# We default to 1 in 1000 requests using compute-at-edge, this can be configured by the edge dictionary
				# named `compute_at_edge_config` and the item in the dictionary named `sample`
				if (var.selected && fastly_info.edge.is_tls && !req.is_background_fetch && !req.is_purge && req.restarts == 0 && fastly.ff.visits_this_service == 0) {
					set req.backend = F_compute_at_edge;
					if (req.backend.healthy) {
						# if using the c@e backend then do not use the cache at all
						return(pass);
					}
				}
			}
		}
	}
}

sub vcl_fetch {
	# if using the compute-at-edge backend, we want to ensure request collapsing is not enabled
	# we don't want request collapsing because request collapsing would cause multiple requests
	# for the same path to be served the same response, which would end up meaning one browser
	# would get a polyfill bundle meant for a different browser
	if (req.backend == F_compute_at_edge) {
		return(pass);
	}
}

include "redirects.vcl";
include "synthetic-responses.vcl";
include "polyfill-service.vcl";

# Finally include the last bit of VCL, this _must_ be last!
include "fastly-boilerplate-end.vcl";


Missing set_next_bottle call

Missing set_next_bottle call

backend F_fake_backend {
	.host = "example.com";
	.port = "80";
	.dynamic = true;
	.connect_timeout = 5s;
	.first_byte_timeout = 15s;
	.probe = {
		.dummy = true;
	}
}

sub vcl_recv {
#FASTLY recv
	set req.backend = F_fake_backend;

	set req.http.X-Bottle = if(req.url ~ "(?i)bottle=([^&]*)", urldecode(re.group.1), "99");
	set req.http.X-Wall = if(req.http.X-Wall, req.http.X-Wall " ", "") req.http.X-Bottle;

	call set_next_bottle;

	if(req.http.X-Bottle == "0") {
		error 996;
	}
	return(lookup);
}

sub vcl_fetch {
#FASTLY fetch

	if(req.restarts > 0) {
		set beresp.http.Fastly-Restarts = req.restarts;
	}
	return(deliver);
}

sub vcl_deliver {
#FASTLY deliver
	set resp.http.X-Wall = req.http.X-Wall;

	if(req.restarts == 2) {
# For now, limit to 3 bottles as we can only restart 3 times
		set req.http.X-Next-Bottle = "0";
	}

	set req.url = "/?bottle=" req.http.X-Next-Bottle;
	if(req.restarts < 2) {
		restart;
	}
	return(deliver);
}

sub set_next_bottle {
	set req.grace = 1d;
	set req.http.X-Original-Grace = req.grace;
	set req.grace = std.atoi(req.http.X-Bottle);
	set req.grace -= 1s;
	set req.http.X-Next-Bottle = regsub(req.grace, "\..*", "");
	set req.grace = std.atoi(req.http.X-Original-Grace);
}

sub vcl_error {
#FASTLY error
	if(obj.status == 996) {
		set obj.status = 200;
		set obj.response = "Cheers!";
		synthetic req.http.X-Wall;
		return (deliver);
	}
}

backend F_fake_backend {
	.host = "fake-backend.example.com";
	.port = "80";
	.dynamic = true;
	.connect_timeout = 5s;
	.first_byte_timeout = 15s;
	.probe = {
		.dummy = true;
	}
}

sub vcl_recv {
#FASTLY recv
	set req.backend = F_fake_backend;

	set req.http.X-Bottle = if(req.url ~ "(?i)bottle=([^&]*)", urldecode(re.group.1), "99");
	set req.http.X-Wall = if(req.http.X-Wall, req.http.X-Wall " ", "") req.http.X-Bottle;

	if(req.http.X-Bottle == "0") {
		error 996;
	}
	return(lookup);
}

sub vcl_fetch {
#FASTLY fetch

	if(req.restarts > 0) {
		set beresp.http.Fastly-Restarts = req.restarts;
	}
	return(deliver);
}

sub vcl_deliver {
#FASTLY deliver
	set resp.http.X-Wall = req.http.X-Wall;

	if(req.restarts == 2) {
# For now, limit to 3 bottles as we can only restart 3 times
		set req.http.X-Next-Bottle = "0";
	}

	set req.url = "/?bottle=" req.http.X-Next-Bottle;
	if(req.restarts < 2) {
		restart;
	}
	return(deliver);
}

sub set_next_bottle {
	set req.grace = 1d;
	set req.http.X-Original-Grace = req.grace;
	set req.grace = std.atoi(req.http.X-Bottle);
	set req.grace -= 1s;
	set req.http.X-Next-Bottle = regsub(req.grace, "\..*", "");
	set req.grace = std.atoi(req.http.X-Original-Grace);
}

sub vcl_error {
#FASTLY error
	if(obj.status == 996) {
		set obj.status = 200;
		set obj.response = "Cheers!";
		synthetic req.http.X-Wall;
		return (deliver);
	}
}


fix varnish force cookie

fix varnish force cookie

vcl 4.0;

# Disable esi xml check on varnish Startup
# Example:
#   DAEMON_OPTS="-a :6081 \
#     -T localhost:6082 \
#     -p feature=+esi_disable_xml_check
#     -p feature=+esi_ignore_https
#

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

sub vcl_recv {
        if (req.method != "GET") {
                return (pass);
        }
        if (req.http.Cookie ~ "\bvarnish-force=pass") {
                return (pass);
        }

        return (hash);
}

sub vcl_hash {
        hash_data(req.url);
        return(lookup);
}

sub vcl_backend_response {
        if (beresp.http.Cache-Control != "public") {
                set beresp.uncacheable = true;
                return (deliver);
        }

        set beresp.do_esi = true;
        set beresp.ttl = 15m;

        unset beresp.http.Set-Cookie;
}

vcl 4.0;

# Disable esi xml check on varnish Startup
# Example:
#   DAEMON_OPTS="-a :6081 \
#     -T localhost:6082 \
#     -p feature=+esi_disable_xml_check
#     -p feature=+esi_ignore_https
#

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

sub vcl_recv {
        if (req.method != "GET") {
                return (pass);
        }
        if (req.http.Cookie ~ "\bvarnish-cookie=pass") {
                return (pass);
        }

        return (hash);
}

sub vcl_hash {
        hash_data(req.url);
        return(lookup);
}

sub vcl_backend_response {
        if (beresp.http.Cache-Control != "public") {
                set beresp.uncacheable = true;
                return (deliver);
        }

        set beresp.do_esi = true;
        set beresp.ttl = 15m;

        unset beresp.http.Set-Cookie;
}


No reason to remove Age and no need to record X-cache since we already do that

    # Add an easy way to see whether custom Fastly VCL has been uploaded
    if ( req.http.Fastly-Debug ) {
        set resp.http.Fastly-Magento-VCL-Uploaded = "1.0.15";
    } else {
        remove resp.http.Fastly-Module-Enabled;
        # remove Varnish/proxy header
        remove resp.http.X-Varnish;
        remove resp.http.Via;
        remove resp.http.X-Purge-URL;
        remove resp.http.X-Purge-Host;
    }

    # Clean up Vary before handing off to the user
    if ( !req.http.Fastly-FF ) {
        set resp.http.Vary = regsub(resp.http.Vary, "Fastly-Cdn-Env,Https", "Cookie");
    }

    if (resp.http.magentomarker && !req.http.Fastly-FF) {
        # Remove the magic marker
        unset resp.http.magentomarker;

        set resp.http.Cache-Control = "no-store, no-cache, must-revalidate, post-check=0, pre-check=0";
        set resp.http.Pragma        = "no-cache";
        set resp.http.Expires       = "Mon, 31 Mar 2008 10:00:00 GMT";
    }

    # Add an easy way to see whether custom Fastly VCL has been uploaded
    if ( req.http.Fastly-Debug ) {
        set resp.http.Fastly-Magento-VCL-Uploaded = "1.0.15";
    } else {
        remove resp.http.Fastly-Module-Enabled;
        # remove Varnish/proxy header
        remove resp.http.X-Varnish;
        remove resp.http.Via;
        remove resp.http.Age;
        remove resp.http.X-Purge-URL;
        remove resp.http.X-Purge-Host;
    }

    # debug info
    if (resp.http.X-Cache-Debug) {
        if (obj.hits > 0) {
            set resp.http.X-Cache      = "HIT";
            set resp.http.X-Cache-Hits = obj.hits;
        } else {
            set resp.http.X-Cache      = "MISS";
        }
        set resp.http.X-Cache-Expires  = resp.http.Expires;
    } else {
    }

    # Clean up Vary before handing off to the user
    if ( !req.http.Fastly-FF ) {
        set resp.http.Vary = regsub(resp.http.Vary, "Fastly-Cdn-Env,Https", "Cookie");
    }

    if (resp.http.magentomarker && !req.http.Fastly-FF) {
        # Remove the magic marker
        unset resp.http.magentomarker;

        set resp.http.Cache-Control = "no-store, no-cache, must-revalidate, post-check=0, pre-check=0";
        set resp.http.Pragma        = "no-cache";
        set resp.http.Expires       = "Mon, 31 Mar 2008 10:00:00 GMT";
    }


Rest of the logic required for per-language cache

C{

/* ------------------------------------------------------------------ */
/* THIS FILE IS AUTOMATICALLY GENERATED BY ./gen_vcl.pl. DO NOT EDIT. */

/*
 * Accept-language header normalization
 *
 * Cosimo, 21/01/2010
 *
 */

#include <ctype.h>  /* isupper */
#include <stdio.h>
#include <stdlib.h> /* qsort */
#include <string.h>

#define DEFAULT_LANGUAGE "en"
#define SUPPORTED_LANGUAGES ":de:fr:es:ru:pt:hy:en:"

#define vcl_string char
#define LANG_LIST_SIZE 16
#define LANG_MAXLEN 16
#define RETURN_LANG(x) { \
    strncpy(lang, x, LANG_MAXLEN); \
    return; \
}
#define RETURN_DEFAULT_LANG RETURN_LANG(DEFAULT_LANGUAGE)
#define PUSH_LANG(x,y) { \
    /* fprintf(stderr, "Pushing lang [%d] %s %.4f\n", curr_lang, x, y); */ \
    /* We have to copy, otherwise root_lang will be the same every time */ \
    strncpy(pl[curr_lang].lang, x, LANG_MAXLEN); \
    pl[curr_lang].q = y;       \
    curr_lang++;               \
}

struct lang_list {
    vcl_string lang[LANG_MAXLEN];
    float q;
};

/* In-place lowercase of a string */
static void strtolower(char *s) {
    register char *c;
    for (c=s; *c; c++) {
        if (isupper(*c)) {
            *c = tolower(*c);
        }
    }
    return;
}

/* Checks if a given language is in the static list of the ones we support */
int is_supported(vcl_string *lang) {
    vcl_string *supported_languages = SUPPORTED_LANGUAGES;
    vcl_string match_str[LANG_MAXLEN + 3] = "";  /* :, :, \0 = 3 */
    int is_supported = 0;

    /* We want to match 'zh-cn' and 'zh-CN' too */
    strtolower(lang);

    /* Search ":<lang>:" in supported languages string */
    strncpy(match_str, ":", 1);
    strncat(match_str, lang, LANG_MAXLEN);
    strncat(match_str, ":\0", 2);

    if (strstr(supported_languages, match_str)) {
        is_supported = 1;
    }

    return is_supported;
}

/* Used by qsort() below */
int sort_by_q(const void *x, const void *y) {
    struct lang_list *a = (struct lang_list *)x;
    struct lang_list *b = (struct lang_list *)y;
    if (a->q > b->q) return -1;
    if (a->q < b->q) return 1;
    return 0;
}

/* Reads Accept-Language, parses it, and finds the first match
   among the supported languages. In case of no match,
   returns the default language.
*/
void select_language(const vcl_string *incoming_header, char *lang) {

    struct lang_list pl[LANG_LIST_SIZE];
    vcl_string *lang_tok = NULL;
    vcl_string root_lang[3];
    vcl_string *header;
    vcl_string *pos = NULL;
    vcl_string *q_spec = NULL;
    unsigned int curr_lang = 0, i = 0;
    float q;

    /* Empty or default string, return default language immediately */
    if (
        !incoming_header
        || (0 == strcmp(incoming_header, "en-US"))
        || (0 == strcmp(incoming_header, "en-GB"))
        || (0 == strcmp(incoming_header, DEFAULT_LANGUAGE))
        || (0 == strcmp(incoming_header, ""))
    )
        RETURN_DEFAULT_LANG;

    /* Tokenize Accept-Language */
    header = (vcl_string *) incoming_header;

    while ((lang_tok = strtok_r(header, " ,", &pos))) {

        q = 1.0;

        if ((q_spec = strstr(lang_tok, ";q="))) {
            /* Truncate language name before ';' */
            *q_spec = '\0';
            /* Get q value */
            sscanf(q_spec + 3, "%f", &q);
        }

        /* Wildcard language '*' should be last in list */
        if ((*lang_tok) == '*') q = 0.0;

        /* Push in the prioritized list */
        PUSH_LANG(lang_tok, q);

        /* For cases like 'en-GB', we also want the root language in the final list */
        if ('-' == lang_tok[2]) {
            root_lang[0] = lang_tok[0];
            root_lang[1] = lang_tok[1];
            root_lang[2] = '\0';
            PUSH_LANG(root_lang, q - 0.001);
        }

        /* For strtok_r() to proceed from where it left off */
        header = NULL;

        /* Break out if stored max no. of languages */
        if (curr_lang >= LANG_MAXLEN) break;
    }

    /* Sort by priority */
    qsort(pl, curr_lang, sizeof(struct lang_list), &sort_by_q);

    /* Match with supported languages */
    for (i = 0; i < curr_lang; i++) {
        if (is_supported(pl[i].lang))
            RETURN_LANG(pl[i].lang);
    }

    RETURN_DEFAULT_LANG;
}

/* Reads req.http.Accept-Language and writes X-Varnish-Accept-Language */
void vcl_rewrite_accept_language(const struct sess *sp) {
    vcl_string *in_hdr;
    vcl_string lang[LANG_MAXLEN];

    memset(lang, 0, LANG_MAXLEN);

    /* Get Accept-Language header from client */
    in_hdr = VRT_GetHdr(sp, HDR_REQ, "\020Accept-Language:");

    /* Normalize and filter out by list of supported languages */
    select_language(in_hdr, lang);

    /* By default, use a different header name: don't mess with backend logic */
    VRT_SetHdr(sp, HDR_REQ, "\032X-Varnish-Accept-Language:", lang, vrt_magic_string_end);

    return;
}

/* vim: syn=c ts=4 et sts=4 sw=4 tw=0
*/

/* THIS FILE IS AUTOMATICALLY GENERATED BY ./gen_vcl.pl. DO NOT EDIT. */
/* ------------------------------------------------------------------ */
}C

sub vcl_recv {
  C{
    vcl_rewrite_accept_language(sp);
  }C
}

sub vcl_fetch {
  set beresp.http.Vary = "X-Varnish-Accept-Language";
}


Make sure we also allow /pub/static

Make sure we also allow /pub/static

  # Check Basic auth against a table. /admin URLs are not basic auth protected to avoid the possibility of people
  # locking themselves out
  if ( table.lookup(magentomodule_basic_auth, regsub(req.http.Authorization, "^Basic ", ""), "NOTFOUND") == "NOTFOUND" &&
      !req.url ~ "^/(index\.php/)?admin(_.*)?/" &&
      !req.url ~ "^/pub/static/" ) {
      error 971;
  }

  # Check Basic auth against a table. /admin URLs are no basic auth protected to avoid the possibility of people
  # locking themselves out
  if ( table.lookup(magentomodule_basic_auth, regsub(req.http.Authorization, "^Basic ", ""), "NOTFOUND") == "NOTFOUND" &&
      req.url !~ "^/(index\.php/)?admin(_.*)?/" ) {
      error 971;
  }