Moved the logic to set a TTL for 404s.

Moved the logic to set a TTL for 404s.

###############################################################################
#
# Fastly CDN for Magento 2
#
# NOTICE OF LICENSE
#
# This source file is subject to the Fastly CDN for Magento 2 End User License
# Agreement that is bundled with this package in the file LICENSE_FASTLY_CDN.txt.
#
# @copyright   Copyright (c) 2016 Fastly, Inc. (http://www.fastly.com)
# @license     BSD, see LICENSE_FASTLY_CDN.txt
#
###############################################################################

# This is a basic VCL configuration file for Fastly CDN for Magento 2 module.

sub vcl_recv {
#FASTLY recv

    # auth for purging
    if (req.request == "FASTLYPURGE") {
        # extract token signature and expiration
        set req.http.X-Sig = regsub(req.http.X-Purge-Token, "^[^_]+_(.*)", "\1");
        set req.http.X-Exp = regsub(req.http.X-Purge-Token, "^([^_]+)_.*", "\1");

        # validate signature
        if (req.http.X-Sig == regsub(digest.hmac_sha1(req.service_id, req.url.path req.http.X-Exp), "^0x", "")) {

            # use vcl time math to check expiration timestamp
            set req.http.X-Original-Grace = req.grace;
            set req.grace = std.atoi(strftime({"%s"}, now));
            set req.grace -= std.atoi(req.http.X-Exp);

            if (std.atoi(req.grace) > 0) {
                error 410;
            }

            # clean up grace since we used it for time math
            set req.grace = std.atoi(req.http.X-Original-Grace);
            unset req.http.X-Original-Grace;

        } else {
            error 403;
        }

        # cleanup variables
        unset req.http.X-Purge-Token;
        unset req.http.X-Sig;
        unset req.http.X-Exp;
    }

    # Deactivate gzip
    set req.http.X-Orig-Accept-Encoding = req.http.Accept-Encoding;
    unset req.http.Accept-Encoding;

    # set HTTPS header for offloaded TLS
    if (req.http.Fastly-SSL) {
        set req.http.Https = "on";
    }

    # disable ESI processing on Origin Shield
    if (req.http.Fastly-FF) {
        set req.esi = false;
    }

    # We only deal with GET and HEAD by default
    if (req.request != "GET" && req.request != "HEAD") {
        return (pass);
    }

    # Bypass shopping cart and checkout requests
    if (req.url ~ "/checkout") {
        return (pass);
    }

    # static files are always cacheable. remove SSL flag and cookie
    if (req.url ~ "^/(pub/)?(media|static)/.*") {
        unset req.http.Https;
        unset req.http.Cookie;
    }

    # geoip lookup
    if (req.url ~ "fastlyCdn/geoip/getaction/") {
        # check if GeoIP has been already processed by client. this normally happens before essential cookies are set.
        if (req.http.cookie ~ "(X-Magento-Vary|form_key)=") {
            error 200 "";
        } else {
            # append parameter with country code
            set req.url = req.url "?country_code=" geoip.country_code;
        }
    }

    return(lookup);
}

sub vcl_fetch {

    # Remove Set-Cookies from responses for static content
    # to match the cookie removal in recv.
    if (req.url ~ "^/(pub/)?(media|static)/.*") {
        unset beresp.http.set-cookie;

        # Set a short TTL for 404's
        if (beresp.status == 404) {
            set beresp.ttl = 300s;
        }

    }

#FASTLY fetch

    if (beresp.status >= 500) {
        # let SOAP errors pass - better debugging
        if (beresp.http.Content-Type ~ "text/xml") {
            return (deliver);
        }

        if (req.restarts < 1 && (req.request == "GET" || req.request == "HEAD")) {
            restart;
        }
    }

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

    if (beresp.http.Content-Type ~ "text/html" || beresp.http.Content-Type ~ "text/xml") {
        # enable ESI feature for Magento response by default
        esi;
    } else {
        # enable gzip for all static content
        if ((beresp.status == 200 || beresp.status == 404) && (beresp.http.content-type ~ "^(text\/html|application\/x\-javascript|text\/css|application\/javascript|text\/javascript|application\/json|application\/vnd\.ms\-fontobject|application\/x\-font\-opentype|application\/x\-font\-truetype|application\/x\-font\-ttf|application\/xml|font\/eot|font\/opentype|font\/otf|image\/svg\+xml|image\/vnd\.microsoft\.icon|text\/plain|text\/xml)\s*($|;)" || req.url ~ "\.(css|js|html|eot|ico|otf|ttf|json)($|\?)" ) ) {
            # always set vary to make sure uncompressed versions dont always win
            if (!beresp.http.Vary ~ "Accept-Encoding") {
                if (beresp.http.Vary) {
                    set beresp.http.Vary = beresp.http.Vary ", Accept-Encoding";
                } else {
                    set beresp.http.Vary = "Accept-Encoding";
                }
            }
            if (req.http.X-Orig-Accept-Encoding == "gzip") {
                set beresp.gzip = true;
            }
        }
    }

    # cache only successfully responses and 404s
    if (beresp.status != 200 && beresp.status != 301 && beresp.status != 404) {
        set req.http.Fastly-Cachetype = "ERROR";
        set beresp.ttl = 1s;
        set beresp.grace = 5s;
        return (deliver);
    } elsif (beresp.http.Cache-Control ~ "private") {
        set req.http.Fastly-Cachetype = "PRIVATE";
        return (pass);
    }

    if (beresp.http.X-Magento-Debug) {
        set beresp.http.X-Magento-Cache-Control = beresp.http.Cache-Control;
    }

    # validate if we need to cache it and prevent from setting cookie
    # images, css and js are cacheable by default so we have to remove cookie also
    if (beresp.ttl > 0s && (req.request == "GET" || req.request == "HEAD")) {
        unset beresp.http.set-cookie;
        if (req.url !~ "^/(pub/)?(media|static)/.*") {
            set beresp.http.Pragma = "no-cache";
            set beresp.http.Expires = "-1";
            set beresp.http.Cache-Control = "no-store, no-cache, must-revalidate, max-age=0";
            set beresp.grace = 1m;
        }

        # init surrogate keys
        if (beresp.http.X-Magento-Tags) {
            set beresp.http.Surrogate-Key = beresp.http.X-Magento-Tags " text";
        } else {
            set beresp.http.Surrogate-Key = "text";
        }

        # set surrogate keys by content type
        if (beresp.http.Content-Type ~ "image") {
            set beresp.http.Surrogate-Key = "image";
        } elsif (beresp.http.Content-Type ~ "script") {
            set beresp.http.Surrogate-Key = "script";
        } elsif (beresp.http.Content-Type ~ "css") {
            set beresp.http.Surrogate-Key = "css";
        }

        set beresp.http.X-Surrogate-Key = beresp.http.Surrogate-Key;
        return (deliver);
    }

    return (deliver);
}

sub vcl_hit {
#FASTLY hit

    if (!obj.cacheable) {
        return(pass);
    }
    return(deliver);
}

sub vcl_miss {
#FASTLY miss

    return(fetch);
}

sub vcl_deliver {
    # debug info
    if (resp.http.X-Magento-Debug) {
        if (obj.hits > 0) {
            set resp.http.X-Magento-Cache-Debug = "HIT";
            set resp.http.X-Magento-Cache-Hits = obj.hits;
        } else {
            set resp.http.X-Magento-Cache-Debug = "MISS";
        }
    } else {
        # remove Varnish/proxy header
        remove resp.http.Age;
        remove resp.http.X-Magento-Debug;
        remove resp.http.X-Magento-Tags;
        remove resp.http.X-Surrogate-Key;
        remove resp.http.X-Magento-Cache-Control;
        remove resp.http.X-Powered-By;
        remove resp.http.Server;
        remove resp.http.X-Varnish;
        remove resp.http.Via;
        remove resp.http.Link;
        remove resp.http.X-Purge-URL;
        remove resp.http.X-Purge-Host;
    }

#FASTLY deliver

    return (deliver);
}

sub vcl_error {
    # workaround for possible security issue
    if (req.url ~ "^\s") {
        set obj.status = 400;
        set obj.response = "Malformed request";
        synthetic "";
        return (deliver);
    }

    # error 200
    if (obj.status == 200) {
        return (deliver);
    }

     set obj.http.Content-Type = "text/html; charset=utf-8";
     set obj.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>"} obj.status " " obj.response {"</title>

Error "} obj.status " " obj.response {"

"} obj.response {"

Guru Meditation:

XID: "} req.xid {"

Fastly CDN server

"};

#FASTLY error
}

sub vcl_pass {
#FASTLY pass
}

sub vcl_hash {
    set req.hash += req.http.Https;
    set req.hash += req.http.host;
    set req.hash += req.url;

    if (req.http.cookie ~ "X-Magento-Vary=") {
        set req.http.X-Magento-Vary = regsub(req.http.cookie, "^.*?X-Magento-Vary=([^;]+);*.*$", "\1");
        set req.hash += req.http.X-Magento-Vary;
        unset req.http.X-Magento-Vary;
    }

    set req.hash += "#####GENERATION#####";

    ### {{ design_exceptions_code }} ###

    return (hash);
}

###############################################################################
#
# Fastly CDN for Magento 2
#
# NOTICE OF LICENSE
#
# This source file is subject to the Fastly CDN for Magento 2 End User License
# Agreement that is bundled with this package in the file LICENSE_FASTLY_CDN.txt.
#
# @copyright   Copyright (c) 2016 Fastly, Inc. (http://www.fastly.com)
# @license     BSD, see LICENSE_FASTLY_CDN.txt
#
###############################################################################

# This is a basic VCL configuration file for Fastly CDN for Magento 2 module.

sub vcl_recv {
#FASTLY recv

    # auth for purging
    if (req.request == "FASTLYPURGE") {
        # extract token signature and expiration
        set req.http.X-Sig = regsub(req.http.X-Purge-Token, "^[^_]+_(.*)", "\1");
        set req.http.X-Exp = regsub(req.http.X-Purge-Token, "^([^_]+)_.*", "\1");

        # validate signature
        if (req.http.X-Sig == regsub(digest.hmac_sha1(req.service_id, req.url.path req.http.X-Exp), "^0x", "")) {

            # use vcl time math to check expiration timestamp
            set req.http.X-Original-Grace = req.grace;
            set req.grace = std.atoi(strftime({"%s"}, now));
            set req.grace -= std.atoi(req.http.X-Exp);

            if (std.atoi(req.grace) > 0) {
                error 410;
            }

            # clean up grace since we used it for time math
            set req.grace = std.atoi(req.http.X-Original-Grace);
            unset req.http.X-Original-Grace;

        } else {
            error 403;
        }

        # cleanup variables
        unset req.http.X-Purge-Token;
        unset req.http.X-Sig;
        unset req.http.X-Exp;
    }

    # Deactivate gzip
    set req.http.X-Orig-Accept-Encoding = req.http.Accept-Encoding;
    unset req.http.Accept-Encoding;

    # set HTTPS header for offloaded TLS
    if (req.http.Fastly-SSL) {
        set req.http.Https = "on";
    }

    # disable ESI processing on Origin Shield
    if (req.http.Fastly-FF) {
        set req.esi = false;
    }

    # We only deal with GET and HEAD by default
    if (req.request != "GET" && req.request != "HEAD") {
        return (pass);
    }

    # Bypass shopping cart and checkout requests
    if (req.url ~ "/checkout") {
        return (pass);
    }

    # static files are always cacheable. remove SSL flag and cookie
    if (req.url ~ "^/(pub/)?(media|static)/.*") {
        unset req.http.Https;
        unset req.http.Cookie;
    }

    # geoip lookup
    if (req.url ~ "fastlyCdn/geoip/getaction/") {
        # check if GeoIP has been already processed by client. this normally happens before essential cookies are set.
        if (req.http.cookie ~ "(X-Magento-Vary|form_key)=") {
            error 200 "";
        } else {
            # append parameter with country code
            set req.url = req.url "?country_code=" geoip.country_code;
        }
    }

    return(lookup);
}

sub vcl_fetch {

    # Remove Set-Cookies from responses for static content
    # to match the cookie removal in recv.
    if (req.url ~ "^/(pub/)?(media|static)/.*") {
        unset beresp.http.set-cookie;

        # Set a short TTL for 404's
        if (beresp.status == 404) {
            set beresp.ttl = 300s;
        }

    }

#FASTLY fetch

    if (beresp.status >= 500) {
        # let SOAP errors pass - better debugging
        if (beresp.http.Content-Type ~ "text/xml") {
            return (deliver);
        }

        if (req.restarts < 1 && (req.request == "GET" || req.request == "HEAD")) {
            restart;
        }
    }

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

    if (beresp.http.Content-Type ~ "text/html" || beresp.http.Content-Type ~ "text/xml") {
        # enable ESI feature for Magento response by default
        esi;
    } else {
        # enable gzip for all static content
        if ((beresp.status == 200 || beresp.status == 404) && (beresp.http.content-type ~ "^(text\/html|application\/x\-javascript|text\/css|application\/javascript|text\/javascript|application\/json|application\/vnd\.ms\-fontobject|application\/x\-font\-opentype|application\/x\-font\-truetype|application\/x\-font\-ttf|application\/xml|font\/eot|font\/opentype|font\/otf|image\/svg\+xml|image\/vnd\.microsoft\.icon|text\/plain|text\/xml)\s*($|;)" || req.url ~ "\.(css|js|html|eot|ico|otf|ttf|json)($|\?)" ) ) {
            # always set vary to make sure uncompressed versions dont always win
            if (!beresp.http.Vary ~ "Accept-Encoding") {
                if (beresp.http.Vary) {
                    set beresp.http.Vary = beresp.http.Vary ", Accept-Encoding";
                } else {
                    set beresp.http.Vary = "Accept-Encoding";
                }
            }
            if (req.http.X-Orig-Accept-Encoding == "gzip") {
                set beresp.gzip = true;
            }
        }
    }

    # cache only successfully responses and 404s
    if (beresp.status != 200 && beresp.status != 301 && beresp.status != 404) {
        set req.http.Fastly-Cachetype = "ERROR";
        set beresp.ttl = 1s;
        set beresp.grace = 5s;
        return (deliver);
    } elsif (beresp.http.Cache-Control ~ "private") {
        set req.http.Fastly-Cachetype = "PRIVATE";
        return (pass);
    }

    if (beresp.http.X-Magento-Debug) {
        set beresp.http.X-Magento-Cache-Control = beresp.http.Cache-Control;
    }

    # validate if we need to cache it and prevent from setting cookie
    # images, css and js are cacheable by default so we have to remove cookie also
    if (beresp.ttl > 0s && (req.request == "GET" || req.request == "HEAD")) {
        unset beresp.http.set-cookie;
        if (req.url !~ "^/(pub/)?(media|static)/.*") {
            set beresp.http.Pragma = "no-cache";
            set beresp.http.Expires = "-1";
            set beresp.http.Cache-Control = "no-store, no-cache, must-revalidate, max-age=0";
            set beresp.grace = 1m;
        }

        # init surrogate keys
        if (beresp.http.X-Magento-Tags) {
            set beresp.http.Surrogate-Key = beresp.http.X-Magento-Tags " text";
        } else {
            set beresp.http.Surrogate-Key = "text";
        }

        # set surrogate keys by content type
        if (beresp.http.Content-Type ~ "image") {
            set beresp.http.Surrogate-Key = "image";
        } elsif (beresp.http.Content-Type ~ "script") {
            set beresp.http.Surrogate-Key = "script";
        } elsif (beresp.http.Content-Type ~ "css") {
            set beresp.http.Surrogate-Key = "css";
        }

        set beresp.http.X-Surrogate-Key = beresp.http.Surrogate-Key;
        return (deliver);
    }

    # Cache 404's for 1 minute.
    if (beresp.status == 404 && req.url ~ "^/(pub/)?(media|static)/.*") {
        set beresp.ttl = 60s;
    }

    return (deliver);
}

sub vcl_hit {
#FASTLY hit

    if (!obj.cacheable) {
        return(pass);
    }
    return(deliver);
}

sub vcl_miss {
#FASTLY miss

    return(fetch);
}

sub vcl_deliver {
    # debug info
    if (resp.http.X-Magento-Debug) {
        if (obj.hits > 0) {
            set resp.http.X-Magento-Cache-Debug = "HIT";
            set resp.http.X-Magento-Cache-Hits = obj.hits;
        } else {
            set resp.http.X-Magento-Cache-Debug = "MISS";
        }
    } else {
        # remove Varnish/proxy header
        remove resp.http.Age;
        remove resp.http.X-Magento-Debug;
        remove resp.http.X-Magento-Tags;
        remove resp.http.X-Surrogate-Key;
        remove resp.http.X-Magento-Cache-Control;
        remove resp.http.X-Powered-By;
        remove resp.http.Server;
        remove resp.http.X-Varnish;
        remove resp.http.Via;
        remove resp.http.Link;
        remove resp.http.X-Purge-URL;
        remove resp.http.X-Purge-Host;
    }

#FASTLY deliver

    return (deliver);
}

sub vcl_error {
    # workaround for possible security issue
    if (req.url ~ "^\s") {
        set obj.status = 400;
        set obj.response = "Malformed request";
        synthetic "";
        return (deliver);
    }

    # error 200
    if (obj.status == 200) {
        return (deliver);
    }

     set obj.http.Content-Type = "text/html; charset=utf-8";
     set obj.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>"} obj.status " " obj.response {"</title>

Error "} obj.status " " obj.response {"

"} obj.response {"

Guru Meditation:

XID: "} req.xid {"

Fastly CDN server

"};

#FASTLY error
}

sub vcl_pass {
#FASTLY pass
}

sub vcl_hash {
    set req.hash += req.http.Https;
    set req.hash += req.http.host;
    set req.hash += req.url;

    if (req.http.cookie ~ "X-Magento-Vary=") {
        set req.http.X-Magento-Vary = regsub(req.http.cookie, "^.*?X-Magento-Vary=([^;]+);*.*$", "\1");
        set req.hash += req.http.X-Magento-Vary;
        unset req.http.X-Magento-Vary;
    }

    set req.hash += "#####GENERATION#####";

    ### {{ design_exceptions_code }} ###

    return (hash);
}


Fixes #1857 (#1858)

Fixes #1857 (#1858)

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", 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 {
		set var.querystring = querystring.set(var.querystring, "version", re.group.1);
	}

	# 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_latest;
		}
		set var.querystring = querystring.set(var.querystring, "ua", req.http.Normalized-User-Agent);
	} else {
		set var.querystring = querystring.set(var.querystring, "ua", 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 "ua_parser.vcl";
include "normalise-user-agent-3-25-1.vcl";
include "normalise-user-agent-latest.vcl";

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

include "breadcrumbs.vcl";
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", 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", 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", 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 {
		set var.querystring = querystring.set(var.querystring, "version", re.group.1);
	}

	# 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_latest;
		}
		set var.querystring = querystring.set(var.querystring, "ua", req.http.Normalized-User-Agent);
	} else {
		set var.querystring = querystring.set(var.querystring, "ua", 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 "ua_parser.vcl";
include "normalise-user-agent-3-25-1.vcl";
include "normalise-user-agent-latest.vcl";

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

include "breadcrumbs.vcl";
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";


esi: true

esi: true

#
# This is an example VCL file for Varnish.
#
# It does not do anything by default, delegating control to the
# builtin VCL. The builtin VCL is called when there is no explicit
# return statement.
#
# See the VCL chapters in the Users Guide at https://www.varnish-cache.org/docs/
# and https://www.varnish-cache.org/trac/wiki/VCLExamples for more examples.

# Marker to tell the VCL compiler that this VCL has been adapted to the
# new 4.0 format.
vcl 4.0;

import directors;

# Default backend definition. Set this to point to your content server.
backend localhost {
    .host = "127.0.0.1";
    .port = "5000";
}

sub vcl_init {
    new backend = directors.round_robin();

    backend.add_backend(localhost);
}

sub vcl_recv {
  set req.backend_hint = backend.backend();

  if (req.method == "INVALIDATE") {
     # ban("obj.http.Hoge ~ ^.*" + req.http.X-Invalidated-Hoge + ".*$");
     ban("obj.response ~ ^.*" + req.http.X-Invalidated-Hoge + ".*$");
    return(synth(200, req.http.X-Invalidated-Hoge + " Purged. Success !!"));
  }

  # if (req.http.Cookie) {
  #     return(pass);
  # }

  if (req.method != "GET" && req.method != "HEAD") {
      return(pass);
  }

  return(hash);
}

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_deliver {
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT";
    } else {
        set resp.http.X-Cache = "MISS";
    }

    return(deliver);
}

sub vcl_backend_response {

  set beresp.do_esi = true;
  # this is same as builtin vcl_backend_response
  # https://www.varnish-cache.org/trac/browser/bin/varnishd/builtin.vcl?rev=ae548683b8f91d0a92799f6c746b80773a4c9f05
  if (beresp.ttl <= 0s ||
    beresp.http.Set-Cookie ||
    beresp.http.Surrogate-control ~ "no-store" ||
    beresp.http.Cache-Control ~ "no-cache" ||
    (!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.uncacheable = true;
  }

  # this is hatena original config
  if (beresp.ttl > 0s && ! beresp.uncacheable) {
      set beresp.grace = 1m;
  }
  return (deliver);
}

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>

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

"} + resp.reason + {"

XID: "} + req.xid + {"

"} );
    return (deliver);
}

#
# This is an example VCL file for Varnish.
#
# It does not do anything by default, delegating control to the
# builtin VCL. The builtin VCL is called when there is no explicit
# return statement.
#
# See the VCL chapters in the Users Guide at https://www.varnish-cache.org/docs/
# and https://www.varnish-cache.org/trac/wiki/VCLExamples for more examples.

# Marker to tell the VCL compiler that this VCL has been adapted to the
# new 4.0 format.
vcl 4.0;

import directors;

# Default backend definition. Set this to point to your content server.
backend localhost {
    .host = "127.0.0.1";
    .port = "5000";
}

sub vcl_init {
    new backend = directors.round_robin();

    backend.add_backend(localhost);
}

sub vcl_recv {
  set req.backend_hint = backend.backend();

  if (req.method == "INVALIDATE") {
     # ban("obj.http.Hoge ~ ^.*" + req.http.X-Invalidated-Hoge + ".*$");
     ban("obj.response ~ ^.*" + req.http.X-Invalidated-Hoge + ".*$");
    return(synth(200, req.http.X-Invalidated-Hoge + " Purged. Success !!"));
  }

  # if (req.http.Cookie) {
  #     return(pass);
  # }

  if (req.method != "GET" && req.method != "HEAD") {
      return(pass);
  }

  return(hash);
}

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_deliver {
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT";
    } else {
        set resp.http.X-Cache = "MISS";
    }

    return(deliver);
}

sub vcl_backend_response {

  # this is same as builtin vcl_backend_response
  # https://www.varnish-cache.org/trac/browser/bin/varnishd/builtin.vcl?rev=ae548683b8f91d0a92799f6c746b80773a4c9f05
  if (beresp.ttl <= 0s ||
    beresp.http.Set-Cookie ||
    beresp.http.Surrogate-control ~ "no-store" ||
    beresp.http.Cache-Control ~ "no-cache" ||
    (!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.uncacheable = true;
  }

  # this is hatena original config
  if (beresp.ttl > 0s && ! beresp.uncacheable) {
      set beresp.grace = 1m;
  }
  return (deliver);
}

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>

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

"} + resp.reason + {"

XID: "} + req.xid + {"

"} );
    return (deliver);
}


Update copyright and author.

Update copyright and author.

#
# Copyright (c) 2016-2018 Varnish Cache project
# Copyright (c) 2012-2016 Varnish Software AS
#
# 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.
#
# detectdevice.vcl - regex based device detection for Varnish
# https://github.com/varnishcache/varnish-devicedetect/
#
# Original author: Lasse Karstensen <lkarsten@varnish-software.com>

sub devicedetect {
	unset req.http.X-UA-Device;
	set req.http.X-UA-Device = "pc";

	# Handle that a cookie may override the detection alltogether.
	if (req.http.Cookie ~ "(?i)X-UA-Device-force") {
		/* ;?? means zero or one ;, non-greedy to match the first. */
		set req.http.X-UA-Device = regsub(req.http.Cookie, "(?i).*X-UA-Device-force=([^;]+);??.*", "\1");
		/* Clean up our mess in the cookie header */
		set req.http.Cookie = regsuball(req.http.Cookie, "(^|; ) *X-UA-Device-force=[^;]+;? *", "\1");
		/* If the cookie header is now empty, or just whitespace, unset it. */
		if (req.http.Cookie ~ "^ *$") { unset req.http.Cookie; }
	} else {
        if (req.http.User-Agent ~ "\(compatible; Googlebot-Mobile/2.1; \+http://www.google.com/bot.html\)" ||
            (req.http.User-Agent ~ "(Android|iPhone)" && req.http.User-Agent ~ "\(compatible.?; Googlebot/2.1.?; \+http://www.google.com/bot.html") ||
			(req.http.User-Agent ~ "(iPhone|Windows Phone)" && req.http.User-Agent ~ "\(compatible; bingbot/2.0; \+http://www.bing.com/bingbot.htm")) {
            set req.http.X-UA-Device = "mobile-bot"; }
		elsif (req.http.User-Agent ~ "(?i)(ads|google|bing|msn|yandex|baidu|ro|career|seznam|)bot" ||
		    req.http.User-Agent ~ "(?i)(baidu|jike|symantec)spider" ||
		    req.http.User-Agent ~ "(?i)pingdom" ||
		    req.http.User-Agent ~ "(?i)facebookexternalhit" ||
		    req.http.User-Agent ~ "(?i)scanner" ||
		    req.http.User-Agent ~ "(?i)slurp" ||
		    req.http.User-Agent ~ "(?i)(web)crawler") {
			set req.http.X-UA-Device = "bot"; }
		elsif (req.http.User-Agent ~ "(?i)ipad")        { set req.http.X-UA-Device = "tablet-ipad"; }
		elsif (req.http.User-Agent ~ "(?i)ip(hone|od)") { set req.http.X-UA-Device = "mobile-iphone"; }
		/* how do we differ between an android phone and an android tablet?
		   http://stackoverflow.com/questions/5341637/how-do-detect-android-tablets-in-general-useragent */
		elsif (req.http.User-Agent ~ "(?i)android.*(mobile|mini)") { set req.http.X-UA-Device = "mobile-android"; }
		// android 3/honeycomb was just about tablet-only, and any phones will probably handle a bigger page layout.
		elsif (req.http.User-Agent ~ "(?i)android 3")              { set req.http.X-UA-Device = "tablet-android"; }
		/* Opera Mobile */
		elsif (req.http.User-Agent ~ "Opera Mobi")                  { set req.http.X-UA-Device = "mobile-smartphone"; }
		// May very well give false positives towards android tablets. Suggestions welcome.
		elsif (req.http.User-Agent ~ "(?i)android")         { set req.http.X-UA-Device = "tablet-android"; }
		elsif (req.http.User-Agent ~ "PlayBook; U; RIM Tablet")         { set req.http.X-UA-Device = "tablet-rim"; }
		elsif (req.http.User-Agent ~ "hp-tablet.*TouchPad")         { set req.http.X-UA-Device = "tablet-hp"; }
		elsif (req.http.User-Agent ~ "Kindle/3")         { set req.http.X-UA-Device = "tablet-kindle"; }
		elsif (req.http.User-Agent ~ "Touch.+Tablet PC" ||
		    req.http.User-Agent ~ "Windows NT [0-9.]+; ARM;" ) {
		        set req.http.X-UA-Device = "tablet-microsoft";
		}
		elsif (req.http.User-Agent ~ "Mobile.+Firefox")     { set req.http.X-UA-Device = "mobile-firefoxos"; }
		elsif (req.http.User-Agent ~ "^HTC" ||
		    req.http.User-Agent ~ "Fennec" ||
		    req.http.User-Agent ~ "IEMobile" ||
		    req.http.User-Agent ~ "BlackBerry" ||
		    req.http.User-Agent ~ "BB10.*Mobile" ||
		    req.http.User-Agent ~ "GT-.*Build/GINGERBREAD" ||
		    req.http.User-Agent ~ "SymbianOS.*AppleWebKit") {
			set req.http.X-UA-Device = "mobile-smartphone";
		}
		elsif (req.http.User-Agent ~ "(?i)symbian" ||
		    req.http.User-Agent ~ "(?i)^sonyericsson" ||
		    req.http.User-Agent ~ "(?i)^nokia" ||
		    req.http.User-Agent ~ "(?i)^samsung" ||
		    req.http.User-Agent ~ "(?i)^lg" ||
		    req.http.User-Agent ~ "(?i)bada" ||
		    req.http.User-Agent ~ "(?i)blazer" ||
		    req.http.User-Agent ~ "(?i)cellphone" ||
		    req.http.User-Agent ~ "(?i)iemobile" ||
		    req.http.User-Agent ~ "(?i)midp-2.0" ||
		    req.http.User-Agent ~ "(?i)u990" ||
		    req.http.User-Agent ~ "(?i)netfront" ||
		    req.http.User-Agent ~ "(?i)opera mini" ||
		    req.http.User-Agent ~ "(?i)palm" ||
		    req.http.User-Agent ~ "(?i)nintendo wii" ||
		    req.http.User-Agent ~ "(?i)playstation portable" ||
		    req.http.User-Agent ~ "(?i)portalmmm" ||
		    req.http.User-Agent ~ "(?i)proxinet" ||
		    req.http.User-Agent ~ "(?i)windows\ ?ce" ||
		    req.http.User-Agent ~ "(?i)winwap" ||
		    req.http.User-Agent ~ "(?i)eudoraweb" ||
		    req.http.User-Agent ~ "(?i)htc" ||
		    req.http.User-Agent ~ "(?i)240x320" ||
		    req.http.User-Agent ~ "(?i)avantgo") {
			set req.http.X-UA-Device = "mobile-generic";
		}
	}
}

# Copyright (c) 2012-2014 Varnish Software AS
#
# 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.
#
# detectdevice.vcl - regex based device detection for Varnish
# https://github.com/varnishcache/varnish-devicedetect/
#
# Author: Lasse Karstensen <lkarsten@varnish-software.com>

sub devicedetect {
	unset req.http.X-UA-Device;
	set req.http.X-UA-Device = "pc";

	# Handle that a cookie may override the detection alltogether.
	if (req.http.Cookie ~ "(?i)X-UA-Device-force") {
		/* ;?? means zero or one ;, non-greedy to match the first. */
		set req.http.X-UA-Device = regsub(req.http.Cookie, "(?i).*X-UA-Device-force=([^;]+);??.*", "\1");
		/* Clean up our mess in the cookie header */
		set req.http.Cookie = regsuball(req.http.Cookie, "(^|; ) *X-UA-Device-force=[^;]+;? *", "\1");
		/* If the cookie header is now empty, or just whitespace, unset it. */
		if (req.http.Cookie ~ "^ *$") { unset req.http.Cookie; }
	} else {
        if (req.http.User-Agent ~ "\(compatible; Googlebot-Mobile/2.1; \+http://www.google.com/bot.html\)" ||
            (req.http.User-Agent ~ "(Android|iPhone)" && req.http.User-Agent ~ "\(compatible.?; Googlebot/2.1.?; \+http://www.google.com/bot.html") ||
			(req.http.User-Agent ~ "(iPhone|Windows Phone)" && req.http.User-Agent ~ "\(compatible; bingbot/2.0; \+http://www.bing.com/bingbot.htm")) {
            set req.http.X-UA-Device = "mobile-bot"; }
		elsif (req.http.User-Agent ~ "(?i)(ads|google|bing|msn|yandex|baidu|ro|career|seznam|)bot" ||
		    req.http.User-Agent ~ "(?i)(baidu|jike|symantec)spider" ||
		    req.http.User-Agent ~ "(?i)pingdom" ||
		    req.http.User-Agent ~ "(?i)facebookexternalhit" ||
		    req.http.User-Agent ~ "(?i)scanner" ||
		    req.http.User-Agent ~ "(?i)slurp" ||
		    req.http.User-Agent ~ "(?i)(web)crawler") {
			set req.http.X-UA-Device = "bot"; }
		elsif (req.http.User-Agent ~ "(?i)ipad")        { set req.http.X-UA-Device = "tablet-ipad"; }
		elsif (req.http.User-Agent ~ "(?i)ip(hone|od)") { set req.http.X-UA-Device = "mobile-iphone"; }
		/* how do we differ between an android phone and an android tablet?
		   http://stackoverflow.com/questions/5341637/how-do-detect-android-tablets-in-general-useragent */
		elsif (req.http.User-Agent ~ "(?i)android.*(mobile|mini)") { set req.http.X-UA-Device = "mobile-android"; }
		// android 3/honeycomb was just about tablet-only, and any phones will probably handle a bigger page layout.
		elsif (req.http.User-Agent ~ "(?i)android 3")              { set req.http.X-UA-Device = "tablet-android"; }
		/* Opera Mobile */
		elsif (req.http.User-Agent ~ "Opera Mobi")                  { set req.http.X-UA-Device = "mobile-smartphone"; }
		// May very well give false positives towards android tablets. Suggestions welcome.
		elsif (req.http.User-Agent ~ "(?i)android")         { set req.http.X-UA-Device = "tablet-android"; }
		elsif (req.http.User-Agent ~ "PlayBook; U; RIM Tablet")         { set req.http.X-UA-Device = "tablet-rim"; }
		elsif (req.http.User-Agent ~ "hp-tablet.*TouchPad")         { set req.http.X-UA-Device = "tablet-hp"; }
		elsif (req.http.User-Agent ~ "Kindle/3")         { set req.http.X-UA-Device = "tablet-kindle"; }
		elsif (req.http.User-Agent ~ "Touch.+Tablet PC" ||
		    req.http.User-Agent ~ "Windows NT [0-9.]+; ARM;" ) {
		        set req.http.X-UA-Device = "tablet-microsoft";
		}
		elsif (req.http.User-Agent ~ "Mobile.+Firefox")     { set req.http.X-UA-Device = "mobile-firefoxos"; }
		elsif (req.http.User-Agent ~ "^HTC" ||
		    req.http.User-Agent ~ "Fennec" ||
		    req.http.User-Agent ~ "IEMobile" ||
		    req.http.User-Agent ~ "BlackBerry" ||
		    req.http.User-Agent ~ "BB10.*Mobile" ||
		    req.http.User-Agent ~ "GT-.*Build/GINGERBREAD" ||
		    req.http.User-Agent ~ "SymbianOS.*AppleWebKit") {
			set req.http.X-UA-Device = "mobile-smartphone";
		}
		elsif (req.http.User-Agent ~ "(?i)symbian" ||
		    req.http.User-Agent ~ "(?i)^sonyericsson" ||
		    req.http.User-Agent ~ "(?i)^nokia" ||
		    req.http.User-Agent ~ "(?i)^samsung" ||
		    req.http.User-Agent ~ "(?i)^lg" ||
		    req.http.User-Agent ~ "(?i)bada" ||
		    req.http.User-Agent ~ "(?i)blazer" ||
		    req.http.User-Agent ~ "(?i)cellphone" ||
		    req.http.User-Agent ~ "(?i)iemobile" ||
		    req.http.User-Agent ~ "(?i)midp-2.0" ||
		    req.http.User-Agent ~ "(?i)u990" ||
		    req.http.User-Agent ~ "(?i)netfront" ||
		    req.http.User-Agent ~ "(?i)opera mini" ||
		    req.http.User-Agent ~ "(?i)palm" ||
		    req.http.User-Agent ~ "(?i)nintendo wii" ||
		    req.http.User-Agent ~ "(?i)playstation portable" ||
		    req.http.User-Agent ~ "(?i)portalmmm" ||
		    req.http.User-Agent ~ "(?i)proxinet" ||
		    req.http.User-Agent ~ "(?i)windows\ ?ce" ||
		    req.http.User-Agent ~ "(?i)winwap" ||
		    req.http.User-Agent ~ "(?i)eudoraweb" ||
		    req.http.User-Agent ~ "(?i)htc" ||
		    req.http.User-Agent ~ "(?i)240x320" ||
		    req.http.User-Agent ~ "(?i)avantgo") {
			set req.http.X-UA-Device = "mobile-generic";
		}
	}
}


update varnish port

update varnish port

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 = "8086";
}

import std;

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

        if (req.url ~ "/esi/header")
        {
                set req.http.Cookie = ";" + req.http.Cookie;
                set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
                set req.http.Cookie = regsuball(req.http.Cookie, ";(PHPSESSID)=", "; \1=");
                set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
                set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
                if (req.http.Cookie == "")
                {
                        unset req.http.Cookie;
                }
        }

        return (hash);
}

sub vcl_hash {
        hash_data(req.http.host);
        hash_data(req.url);
        if (req.url ~ "/esi/header/")
        {
                hash_data(req.http.Cookie);
        }
        return(lookup);
}

sub vcl_backend_response {
        if (bereq.url ~ "/esi/header/")
        {
                unset beresp.http.Pragma;
                unset beresp.http.Cache-control;
                set beresp.ttl = 30m;
        }
        else if (beresp.http.Cache-Control != "public")
        {
                set beresp.uncacheable = true;
                set beresp.ttl = 3m;
                return (deliver);
        }

        set beresp.do_esi = true;
        set beresp.ttl = std.duration(beresp.http.Varnish-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";
}

import std;

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

        if (req.url ~ "/esi/header")
        {
                set req.http.Cookie = ";" + req.http.Cookie;
                set req.http.Cookie = regsuball(req.http.Cookie, "; +", ";");
                set req.http.Cookie = regsuball(req.http.Cookie, ";(PHPSESSID)=", "; \1=");
                set req.http.Cookie = regsuball(req.http.Cookie, ";[^ ][^;]*", "");
                set req.http.Cookie = regsuball(req.http.Cookie, "^[; ]+|[; ]+$", "");
                if (req.http.Cookie == "")
                {
                        unset req.http.Cookie;
                }
        }

        return (hash);
}

sub vcl_hash {
        hash_data(req.http.host);
        hash_data(req.url);
        if (req.url ~ "/esi/header/")
        {
                hash_data(req.http.Cookie);
        }
        return(lookup);
}

sub vcl_backend_response {
        if (bereq.url ~ "/esi/header/")
        {
                unset beresp.http.Pragma;
                unset beresp.http.Cache-control;
                set beresp.ttl = 30m;
        }
        else if (beresp.http.Cache-Control != "public")
        {
                set beresp.uncacheable = true;
                set beresp.ttl = 3m;
                return (deliver);
        }

        set beresp.do_esi = true;
        set beresp.ttl = std.duration(beresp.http.Varnish-TTL, 15m);

        unset beresp.http.Set-Cookie;
}


Wrong obj.status for force TLS

Wrong obj.status for force TLS

# Force SSL immediately to avoid magento module VCL stripping off
# google campaign ids like gclid
  if (!req.http.Fastly-SSL) {
    error 801 "Force SSL";
  }

# Force SSL immediately to avoid magento module VCL stripping off
# google campaign ids like gclid
  if (!req.http.Fastly-SSL) {
    error 972 "Force SSL";
  }


Allow to cache search results

Allow to cache search results

See https://github.com/magento/magento2/pull/30340

    # When using Magento tester to test whether your site is configured properly
    # this uses a bypass secret. By default we will use service ID as the bypass secret
    # however user can override this by defining a bypass_secret key in the
    # magentomodule_config edge dictionary
    if ( req.http.bypass-secret ) {
        declare local var.bypass-secret STRING;
        set var.bypass-secret = table.lookup(magentomodule_config, "bypass_secret", "NONE");
        if ( var.bypass-secret == req.http.bypass-secret ) {
            set req.http.x-pass = "1";
            set var.fastly_req_do_shield = false;
            set req.hash_always_miss = true;
        } else if ( var.bypass-secret == "NONE" && req.http.bypass-secret == req.service_id ) {
            set req.http.x-pass = "1";
            set var.fastly_req_do_shield = false;
            set req.hash_always_miss = true;
        } else {
            error 403 "Bypass Secret incorrect";
        }
    }

    # VCL required to support maintenance mode. Don't maintenance mode admin pages and supporting assets
    if (table.lookup(magentomodule_config, "allow_super_users_during_maint", "0") == "1" &&
        !req.http.Fastly-Client-Ip ~ maint_allowlist &&
        !req.url ~ "^/(index\.php/)?####ADMIN_PATH####/" &&
        !req.url ~ "^/pub/static/") {

        # If we end up here after a restart and there is a ResponseObject it means we got here after error
        # page VCL restart. We shouldn't touch it. Otherwise return a plain 503 error page
        if ( req.restarts > 0 && req.http.ResponseObject ) {
            set req.http.ResponseObject = "970";
        } else {
            error 503 "Maintenance mode";
        }
    }

    # Fixup for Varnish ESI not dealing with https:// absolute URLs well
    if (req.is_esi_subreq && req.url ~ "/https://([^/]+)(/.*)$") {
        set req.http.Host = re.group.1;
        set req.url = re.group.2;
    }

    unset req.http.x-long-cache;

    # We want to force long cache times on any of the versioned assets
    if (req.url.path ~ "^/static/version\d*/") {
       set req.http.x-long-cache = "1";
    }

    # User's Cookie may contain some Magento Vary items we should vary on
    if (req.http.cookie:X-Magento-Vary) {
        set req.http.X-Magento-Vary = req.http.cookie:X-Magento-Vary;
    } else {
        unset req.http.X-Magento-Vary;
    }

    ############################################################################################################
    # Following code block controls purge by URL. By default we want to protect all URL purges. In general this
    # is addressed by adding Fastly-Purge-Requires-Auth request header in vcl_recv however this runs the risk of
    # exposing API tokens if user attempts to purge non-https URLs. For this reason inside the Magento module
    # we use X-Purge-Token. Unfortunately this breaks purge from the Fastly UI. Therefore in the next code block
    # we check for presence of X-Purge-Token. If it's not present we force the Fastly-Purge-Requires-Auth
    if (req.request == "FASTLYPURGE") {
        # extract token signature and expiration
        if (req.http.X-Purge-Token && req.http.X-Purge-Token ~ "^([^_]+)_(.*)" ) {

            declare local var.X-Exp STRING;
            declare local var.X-Sig STRING;
            /* extract token expiration and signature */
            set var.X-Exp = re.group.1;
            set var.X-Sig = re.group.2;

            /* validate signature */
            if (var.X-Sig == regsub(digest.hmac_sha1(req.service_id, req.url.path var.X-Exp), "^0x", "")) {
            /* check that expiration time has not elapsed */
                if (time.is_after(now, std.integer2time(std.atoi(var.X-Exp)))) {
                    error 410;
                }
            } else {
                error 403;
            }
        } else {
            set req.http.Fastly-Purge-Requires-Auth = "1";
        }
    }

    # set HTTPS header for offloaded TLS
    if (req.http.Fastly-SSL) {
        set req.http.Https = "on";
    }

    if (fastly.ff.visits_this_service > 0) {
        # disable ESI processing on Origin Shield
        set req.esi = false;
        # Needed for proper handling of stale while revalidated when shielding is involved
        set req.max_stale_while_revalidate = 0s;
    }

    # Make sure we lookup end user geo not shielding. More at https://docs.fastly.com/vcl/geolocation/#using-geographic-variables-with-shielding
    set client.geo.ip_override = req.http.Fastly-Client-IP;

    # geoip lookup
    if (req.url.path ~ "fastlyCdn/geoip/getaction/") {
        # check if GeoIP has been already processed by client. this normally happens before essential cookies are set.
        if (req.http.cookie:X-Magento-Vary || req.http.cookie:form_key) {
            error 980 "GeoIP already processed";
        } else {
            # append parameter with country code only if it doesn't exist already
            if ( req.url.qs !~ "country_code=" ) {
                set req.url = querystring.set(req.url, "country_code", if ( req.http.geo_override, req.http.geo_override, client.geo.country_code));
            }
        }
    } else if ( req.url.path !~ "/graphql" ) {
        # Per suggestions in https://github.com/sdinteractive/SomethingDigital_PageCacheParams
        # we'll strip out query parameters used in Google AdWords, Mailchimp tracking by default
        # and allow custom parameters to be set. List of parameters is configurable in admin
        set req.http.Magento-Original-URL = req.url;
        # Change the list of ignored parameters by configuring them in the Advanced section
        set req.url = querystring.regfilter(req.url, "^(####QUERY_PARAMETERS####)");
    }

    # Don't allow clients to force a pass
    if (req.restarts == 0) {
        unset req.http.x-pass;
        unset req.http.Rate-Limit;
        unset req.http.magento-admin-path;
    }

    # Pass on checkout URLs. Because it's a snippet we want to execute this after backend selection so we handle it
    # in the request condition
    if (!req.http.x-long-cache && req.url ~ "/(checkout|customer/section/load)") {
        set req.http.x-pass = "1";
    # Pass all admin actions
    # ####ADMIN_PATH#### is replaced with value of frontName from app/etc/env.php
    } else if ( req.url ~ "^/(index\.php/)?####ADMIN_PATH####/" ) {
        set req.http.x-pass = "1";
        set req.http.magento-admin-path = "1";
    } else {
        # Sort the query arguments to increase cache hit ratio with query arguments that
        # may be out od order. In case you want to restore the unsorted URL add a snippet
        # after this one that sets req.url to req.http.Magento-Original-URL
        if ( !req.http.x-pass ) {
            set req.url = boltsort.sort(req.url);
        }
    }

    # static files are always cacheable. remove SSL flag and cookie
    if (req.http.x-long-cache || req.url ~ "^/(pub/)?(media|static)/.*") {
        unset req.http.Https;
        unset req.http.Cookie;
    }

    unset req.http.graphql;
    # GraphQL special headers handling because this area doesn't rely on X-Magento-Vary cookie
    if (req.request == "GET" && req.url.path ~ "/graphql" && req.url.qs ~ "query=") {
        if ( req.http.Authorization ~ "^Bearer" ) {
            set req.http.x-pass = "1";
        } else {
            set req.http.graphql = "1";
            if (req.http.Store) {
                set req.http.X-Magento-Vary = req.http.Store;
            }
            if (req.http.Content-Currency) {
                if (req.http.X-Magento-Vary) {
                    set req.http.X-Magento-Vary = req.http.X-Magento-Vary req.http.Content-Currency;
                } else {
                    set req.http.X-Magento-Vary = req.http.Content-Currency;
                }
            }
        }
    }

    # When using Magento tester to test whether your site is configured properly
    # this uses a bypass secret. By default we will use service ID as the bypass secret
    # however user can override this by defining a bypass_secret key in the
    # magentomodule_config edge dictionary
    if ( req.http.bypass-secret ) {
        declare local var.bypass-secret STRING;
        set var.bypass-secret = table.lookup(magentomodule_config, "bypass_secret", "NONE");
        if ( var.bypass-secret == req.http.bypass-secret ) {
            set req.http.x-pass = "1";
            set var.fastly_req_do_shield = false;
            set req.hash_always_miss = true;
        } else if ( var.bypass-secret == "NONE" && req.http.bypass-secret == req.service_id ) {
            set req.http.x-pass = "1";
            set var.fastly_req_do_shield = false;
            set req.hash_always_miss = true;
        } else {
            error 403 "Bypass Secret incorrect";
        }
    }

    # VCL required to support maintenance mode. Don't maintenance mode admin pages and supporting assets
    if (table.lookup(magentomodule_config, "allow_super_users_during_maint", "0") == "1" &&
        !req.http.Fastly-Client-Ip ~ maint_allowlist &&
        !req.url ~ "^/(index\.php/)?####ADMIN_PATH####/" &&
        !req.url ~ "^/pub/static/") {

        # If we end up here after a restart and there is a ResponseObject it means we got here after error
        # page VCL restart. We shouldn't touch it. Otherwise return a plain 503 error page
        if ( req.restarts > 0 && req.http.ResponseObject ) {
            set req.http.ResponseObject = "970";
        } else {
            error 503 "Maintenance mode";
        }
    }

    # Fixup for Varnish ESI not dealing with https:// absolute URLs well
    if (req.is_esi_subreq && req.url ~ "/https://([^/]+)(/.*)$") {
        set req.http.Host = re.group.1;
        set req.url = re.group.2;
    }

    unset req.http.x-long-cache;

    # We want to force long cache times on any of the versioned assets
    if (req.url.path ~ "^/static/version\d*/") {
       set req.http.x-long-cache = "1";
    }

    # User's Cookie may contain some Magento Vary items we should vary on
    if (req.http.cookie:X-Magento-Vary) {
        set req.http.X-Magento-Vary = req.http.cookie:X-Magento-Vary;
    } else {
        unset req.http.X-Magento-Vary;
    }

    ############################################################################################################
    # Following code block controls purge by URL. By default we want to protect all URL purges. In general this
    # is addressed by adding Fastly-Purge-Requires-Auth request header in vcl_recv however this runs the risk of
    # exposing API tokens if user attempts to purge non-https URLs. For this reason inside the Magento module
    # we use X-Purge-Token. Unfortunately this breaks purge from the Fastly UI. Therefore in the next code block
    # we check for presence of X-Purge-Token. If it's not present we force the Fastly-Purge-Requires-Auth
    if (req.request == "FASTLYPURGE") {
        # extract token signature and expiration
        if (req.http.X-Purge-Token && req.http.X-Purge-Token ~ "^([^_]+)_(.*)" ) {

            declare local var.X-Exp STRING;
            declare local var.X-Sig STRING;
            /* extract token expiration and signature */
            set var.X-Exp = re.group.1;
            set var.X-Sig = re.group.2;

            /* validate signature */
            if (var.X-Sig == regsub(digest.hmac_sha1(req.service_id, req.url.path var.X-Exp), "^0x", "")) {
            /* check that expiration time has not elapsed */
                if (time.is_after(now, std.integer2time(std.atoi(var.X-Exp)))) {
                    error 410;
                }
            } else {
                error 403;
            }
        } else {
            set req.http.Fastly-Purge-Requires-Auth = "1";
        }
    }

    # set HTTPS header for offloaded TLS
    if (req.http.Fastly-SSL) {
        set req.http.Https = "on";
    }

    if (fastly.ff.visits_this_service > 0) {
        # disable ESI processing on Origin Shield
        set req.esi = false;
        # Needed for proper handling of stale while revalidated when shielding is involved
        set req.max_stale_while_revalidate = 0s;
    }

    # Make sure we lookup end user geo not shielding. More at https://docs.fastly.com/vcl/geolocation/#using-geographic-variables-with-shielding
    set client.geo.ip_override = req.http.Fastly-Client-IP;

    # geoip lookup
    if (req.url.path ~ "fastlyCdn/geoip/getaction/") {
        # check if GeoIP has been already processed by client. this normally happens before essential cookies are set.
        if (req.http.cookie:X-Magento-Vary || req.http.cookie:form_key) {
            error 980 "GeoIP already processed";
        } else {
            # append parameter with country code only if it doesn't exist already
            if ( req.url.qs !~ "country_code=" ) {
                set req.url = querystring.set(req.url, "country_code", if ( req.http.geo_override, req.http.geo_override, client.geo.country_code));
            }
        }
    } else if ( req.url.path !~ "/graphql" ) {
        # Per suggestions in https://github.com/sdinteractive/SomethingDigital_PageCacheParams
        # we'll strip out query parameters used in Google AdWords, Mailchimp tracking by default
        # and allow custom parameters to be set. List of parameters is configurable in admin
        set req.http.Magento-Original-URL = req.url;
        # Change the list of ignored parameters by configuring them in the Advanced section
        set req.url = querystring.regfilter(req.url, "^(####QUERY_PARAMETERS####)");
    }

    # Don't allow clients to force a pass
    if (req.restarts == 0) {
        unset req.http.x-pass;
        unset req.http.Rate-Limit;
        unset req.http.magento-admin-path;
    }

    # Pass on checkout URLs. Because it's a snippet we want to execute this after backend selection so we handle it
    # in the request condition
    if (!req.http.x-long-cache && req.url ~ "/(catalogsearch|checkout|customer/section/load)") {
        set req.http.x-pass = "1";
    # Pass all admin actions
    # ####ADMIN_PATH#### is replaced with value of frontName from app/etc/env.php
    } else if ( req.url ~ "^/(index\.php/)?####ADMIN_PATH####/" ) {
        set req.http.x-pass = "1";
        set req.http.magento-admin-path = "1";
    } else {
        # Sort the query arguments to increase cache hit ratio with query arguments that
        # may be out od order. In case you want to restore the unsorted URL add a snippet
        # after this one that sets req.url to req.http.Magento-Original-URL
        if ( !req.http.x-pass ) {
            set req.url = boltsort.sort(req.url);
        }
    }

    # static files are always cacheable. remove SSL flag and cookie
    if (req.http.x-long-cache || req.url ~ "^/(pub/)?(media|static)/.*") {
        unset req.http.Https;
        unset req.http.Cookie;
    }

    unset req.http.graphql;
    # GraphQL special headers handling because this area doesn't rely on X-Magento-Vary cookie
    if (req.request == "GET" && req.url.path ~ "/graphql" && req.url.qs ~ "query=") {
        if ( req.http.Authorization ~ "^Bearer" ) {
            set req.http.x-pass = "1";
        } else {
            set req.http.graphql = "1";
            if (req.http.Store) {
                set req.http.X-Magento-Vary = req.http.Store;
            }
            if (req.http.Content-Currency) {
                if (req.http.X-Magento-Vary) {
                    set req.http.X-Magento-Vary = req.http.X-Magento-Vary req.http.Content-Currency;
                } else {
                    set req.http.X-Magento-Vary = req.http.Content-Currency;
                }
            }
        }
    }


Removing health checks for the moment

Removing health checks for the moment

vcl 4.0;
import std;

backend default {
    .host = "::BACKEND_PORT_8000_TCP_ADDR::";
    .port = "::BACKEND_PORT_8000_TCP_PORT::";
    # .probe = {
    #     .url = "/";
    #     .interval = 5s;
    #     .timeout = 1 s;
    #     .window = 5;
    #     .threshold = 3;
    # }
}

sub vcl_recv {
    # Ignore all cookies except for /admin
    if (!(req.url ~ "^/admin/")) {
        unset req.http.Cookie;
    }

    # # Give ourself some restart time
    # set req.backend_hint = default;
    # if (! std.healthy(req.backend)) {
    #     set req.grace = 5m;
    # } else {
    #     set req.grace = 15s;
    # }
}

sub vcl_backend_response {
    # Unset any cookies for static content and cache for 1 hour
    if (bereq.url ~ "^/static/") {
        unset beresp.http.set-cookie;
        set beresp.ttl = 1h;
    }
    # # Add some grace for backend restarts
    # set beresp.grace = 30m;
}

vcl 4.0;
import std;

backend default {
    .host = "::BACKEND_PORT_8000_TCP_ADDR::";
    .port = "::BACKEND_PORT_8000_TCP_PORT::";
    .probe = {
        .url = "/";
        .interval = 5s;
        .timeout = 1 s;
        .window = 5;
        .threshold = 3;
    }
}

sub vcl_recv {
    # Ignore all cookies except for /admin
    if (!(req.url ~ "^/admin/")) {
        unset req.http.Cookie;
    }

    # # Give ourself some restart time
    # set req.backend_hint = default;
    # if (! std.healthy(req.backend)) {
    #     set req.grace = 5m;
    # } else {
    #     set req.grace = 15s;
    # }
}

sub vcl_backend_response {
    # Unset any cookies for static content and cache for 1 hour
    if (bereq.url ~ "^/static/") {
        unset beresp.http.set-cookie;
        set beresp.ttl = 1h;
    }
    # # Add some grace for backend restarts
    # set beresp.grace = 30m;
}


Use r2 for all JSON, RSS, and XML related endpoints.

backend mweb {
    .host = "mweb";
    .port = "4444";
}
/* backend www {
    .host = "www.reddit.com";
    .port = "80";
} */

sub vcl_recv {
  call device_detect;

  # Check mweb override cookie.
  if (req.http.Cookie ~ "(^|;\s*)(mweb-no-redirect=1)(;|$)") {
    call set_www_backend;
  } elsif (
    (req.http.X-UA-Device ~ "^mobile-" || req.http.X-UA-Device ~ "^tablet-")
    # We don't host endpoints with extensions in mweb.
    # The Fastly equivalent is: && !req.url.ext
    && !reg.url ~ "\/.+\.[a-z]{3,4}(\?.*|$)"
    && (
      # Blacklisted endpoints
      # This endpoint 200's for mweb, but the current implementation has issues
      req.url !~ "^/message/compose/?$"
    )
    && (
      # Whitelisted 2X endpoints from router/index.js
      req.url == "/"
      || req.url ~ "^/actions/"
      || req.url ~ "^/comments/?"
      || req.url ~ "^/login/?$"
      || req.url ~ "^/message/"
      || req.url ~ "^/r/"
      || req.url ~ "^/register/?$"
      || req.url ~ "^/report/?$"
      || req.url ~ "^/search/?$"
      || req.url ~ "^/submit/?$"
      || req.url ~ "^/submit_to_community/?$"
      || req.url ~ "^/u/"
      || req.url ~ "^/user/"
      || req.url ~ "^/vote/"
      || req.url ~ "^/(help|w|wiki)/?"
      # Whitelisted 2X Ajax endpoints
      || req.url ~ "^/csp-report$"
      || req.url == "/error"
      || req.url ~ "^/(login|refresh|register)proxy$"
      || req.url ~ "^/logout"
      || req.url == "/routes"
      || req.url ~ "^/timings$"
      || req.url ~ "^/(u/)?XXX\?.*"
      # Whitelisted dev endpoints (these will be hosted on www in production)
      || req.url == "/apple-app-site-association"
      || req.url ~ "^/favicon/"
      || req.url ~ "^/favicon\.ico"
      || req.url ~ "^/fonts/"
      || req.url == "/health"
      || req.url ~ "^/img/"
      || req.url ~ "^/ProductionClient\..*\.(css|js)$"
      || req.url == "/robots.txt"
  )) {
    # If it's a mobile device and a whitelisted endpoint, use mweb.
    set req.backend = mweb;
  } else {
    call set_www_backend;
  }
}

sub set_www_backend {
  # NOTE(wting|2016-09-26): Normally we would redirect the user to a different
  # backend, but instead we're using a 400 status code for easy programmatic testing.
  error 400;
  # set req.backend = www;
}

# Vendorized from https://github.com/varnish/varnish-devicedetect/blob/master/devicedetect.vcl
# BSD-2 License
sub device_detect {
  unset req.http.X-UA-Device;
  set req.http.X-UA-Device = "pc";

  # Handle that a cookie may override the detection alltogether.
  if (req.http.Cookie ~ "(?i)X-UA-Device-force") {
    /* ;?? means zero or one ;, non-greedy to match the first. */
    set req.http.X-UA-Device = regsub(req.http.Cookie, "(?i).*X-UA-Device-force=([^;]+);??.*", "\1");
    /* Clean up our mess in the cookie header */
    set req.http.Cookie = regsuball(req.http.Cookie, "(^|; ) *X-UA-Device-force=[^;]+;? *", "\1");
    /* If the cookie header is now empty, or just whitespace, unset it. */
    if (req.http.Cookie ~ "^ *$") {
      unset req.http.Cookie;
    }
  } else {
    if (req.http.User-Agent ~ "\(compatible; Googlebot-Mobile/2.1; \+http://www.google.com/bot.html\)"
        || (req.http.User-Agent ~ "(Android|iPhone)"
            && req.http.User-Agent ~ "\(compatible.?; Googlebot/2.1.?; \+http://www.google.com/bot.html")
        || (req.http.User-Agent ~ "(iPhone|Windows Phone)"
            && req.http.User-Agent ~ "\(compatible; bingbot/2.0; \+http://www.bing.com/bingbot.htm")
    ) {
      set req.http.X-UA-Device = "mobile-bot";
    } elsif (req.http.User-Agent ~ "(?i)(ads|google|bing|msn|yandex|baidu|ro|career|seznam|)bot"
             || req.http.User-Agent ~ "(?i)(baidu|jike|symantec)spider"
             || req.http.User-Agent ~ "(?i)scanner"
             || req.http.User-Agent ~ "(?i)(web)crawler"
    ) {
      set req.http.X-UA-Device = "bot";
    } elsif (req.http.User-Agent ~ "(?i)ipad") {
      set req.http.X-UA-Device = "tablet-ipad";
    } elsif (req.http.User-Agent ~ "(?i)ip(hone|od)") {
      set req.http.X-UA-Device = "mobile-iphone";
    } elsif (req.http.User-Agent ~ "(?i)android.*(mobile|mini)") {
      /* how do we differ between an android phone and an android tablet? */
      /* http://stackoverflow.com/questions/5341637/how-do-detect-android-tablets-in-general-useragent */
      set req.http.X-UA-Device = "mobile-android";
    }
    elsif (req.http.User-Agent ~ "(?i)android 3") {
      /* https://github.com/varnish/varnish-devicedetect/blob/master/devicedetect.vcl */
      set req.http.X-UA-Device = "tablet-android";
    } elsif (req.http.User-Agent ~ "Opera Mobi") {
      /* Opera Mobile */
      set req.http.X-UA-Device = "mobile-smartphone";
    }
    elsif (req.http.User-Agent ~ "(?i)android") {
      /* May very well give false positives towards android tablets. Suggestions welcome. */
      set req.http.X-UA-Device = "tablet-android";
    } elsif (req.http.User-Agent ~ "PlayBook; U; RIM Tablet") {
      set req.http.X-UA-Device = "tablet-rim";
    } elsif (req.http.User-Agent ~ "hp-tablet.*TouchPad") {
      set req.http.X-UA-Device = "tablet-hp";
    } elsif (req.http.User-Agent ~ "Kindle/3") {
      set req.http.X-UA-Device = "tablet-kindle";
    } elsif (req.http.User-Agent ~ "Touch.+Tablet PC" || req.http.User-Agent ~ "Windows NT [0-9.]+; ARM;" ) {
      set req.http.X-UA-Device = "tablet-microsoft";
    } elsif (req.http.User-Agent ~ "Mobile.+Firefox") {
      set req.http.X-UA-Device = "mobile-firefoxos";
    } elsif (req.http.User-Agent ~ "^HTC"
             || req.http.User-Agent ~ "Fennec"
             || req.http.User-Agent ~ "IEMobile"
             || req.http.User-Agent ~ "BlackBerry"
             || req.http.User-Agent ~ "BB10.*Mobile"
             || req.http.User-Agent ~ "GT-.*Build/GINGERBREAD"
             || req.http.User-Agent ~ "SymbianOS.*AppleWebKit") {
      set req.http.X-UA-Device = "mobile-smartphone";
    }
    elsif (req.http.User-Agent ~ "(?i)symbian"
           || req.http.User-Agent ~ "(?i)^sonyericsson"
           || req.http.User-Agent ~ "(?i)^nokia"
           || req.http.User-Agent ~ "(?i)^samsung"
           || req.http.User-Agent ~ "(?i)^lg"
           || req.http.User-Agent ~ "(?i)bada"
           || req.http.User-Agent ~ "(?i)blazer"
           || req.http.User-Agent ~ "(?i)cellphone"
           || req.http.User-Agent ~ "(?i)iemobile"
           || req.http.User-Agent ~ "(?i)midp-2.0"
           || req.http.User-Agent ~ "(?i)u990"
           || req.http.User-Agent ~ "(?i)netfront"
           || req.http.User-Agent ~ "(?i)opera mini"
           || req.http.User-Agent ~ "(?i)palm"
           || req.http.User-Agent ~ "(?i)nintendo wii"
           || req.http.User-Agent ~ "(?i)playstation portable"
           || req.http.User-Agent ~ "(?i)portalmmm"
           || req.http.User-Agent ~ "(?i)proxinet"
           || req.http.User-Agent ~ "(?i)sonyericsson"
           || req.http.User-Agent ~ "(?i)symbian"
           || req.http.User-Agent ~ "(?i)windows\ ?ce"
           || req.http.User-Agent ~ "(?i)winwap"
           || req.http.User-Agent ~ "(?i)eudoraweb"
           || req.http.User-Agent ~ "(?i)htc"
           || req.http.User-Agent ~ "(?i)240x320"
           || req.http.User-Agent ~ "(?i)avantgo") {
      set req.http.X-UA-Device = "mobile-generic";
    }
  }
}

backend mweb {
    .host = "mweb";
    .port = "4444";
}
/* backend www {
    .host = "www.reddit.com";
    .port = "80";
} */

sub vcl_recv {
  call device_detect;

  # Check mweb override cookie.
  if (req.http.Cookie ~ "(^|;\s*)(mweb-no-redirect=1)(;|$)") {
    call set_www_backend;
  } elsif (
    (req.http.X-UA-Device ~ "^mobile-" || req.http.X-UA-Device ~ "^tablet-")
    && (
      # Blacklisted endpoints
      # This endpoint 200's for mweb, but the current implementation has issues
      req.url !~ "^/message/compose/?$"
    )
    && (
      # Whitelisted 2X endpoints from router/index.js
      req.url == "/"
      || req.url ~ "^/actions/"
      || req.url ~ "^/comments/?"
      || req.url ~ "^/login/?$"
      || req.url ~ "^/message/"
      || req.url ~ "^/r/"
      || req.url ~ "^/register/?$"
      || req.url ~ "^/report/?$"
      || req.url ~ "^/search/?$"
      || req.url ~ "^/submit/?$"
      || req.url ~ "^/submit_to_community/?$"
      || req.url ~ "^/u/"
      || req.url ~ "^/user/"
      || req.url ~ "^/vote/"
      || req.url ~ "^/(help|w|wiki)/?"
      # Whitelisted 2X Ajax endpoints
      || req.url ~ "^/csp-report$"
      || req.url == "/error"
      || req.url ~ "^/(login|refresh|register)proxy$"
      || req.url ~ "^/logout"
      || req.url == "/routes"
      || req.url ~ "^/timings$"
      || req.url ~ "^/(u/)?XXX\?.*"
      # Whitelisted dev endpoints (these will be hosted on www in production)
      || req.url == "/apple-app-site-association"
      || req.url ~ "^/favicon/"
      || req.url ~ "^/favicon\.ico"
      || req.url ~ "^/fonts/"
      || req.url == "/health"
      || req.url ~ "^/img/"
      || req.url ~ "^/ProductionClient\..*\.(css|js)$"
      || req.url == "/robots.txt"
  )) {
    # If it's a mobile device and a whitelisted endpoint, use mweb.
    set req.backend = mweb;
  } else {
    call set_www_backend;
  }
}

sub set_www_backend {
  # NOTE(wting|2016-09-26): Normally we would redirect the user to a different
  # backend, but instead we're using a 400 status code for easy programmatic testing.
  error 400;
  # set req.backend = www;
}

# Vendorized from https://github.com/varnish/varnish-devicedetect/blob/master/devicedetect.vcl
# BSD-2 License
sub device_detect {
  unset req.http.X-UA-Device;
  set req.http.X-UA-Device = "pc";

  # Handle that a cookie may override the detection alltogether.
  if (req.http.Cookie ~ "(?i)X-UA-Device-force") {
    /* ;?? means zero or one ;, non-greedy to match the first. */
    set req.http.X-UA-Device = regsub(req.http.Cookie, "(?i).*X-UA-Device-force=([^;]+);??.*", "\1");
    /* Clean up our mess in the cookie header */
    set req.http.Cookie = regsuball(req.http.Cookie, "(^|; ) *X-UA-Device-force=[^;]+;? *", "\1");
    /* If the cookie header is now empty, or just whitespace, unset it. */
    if (req.http.Cookie ~ "^ *$") {
      unset req.http.Cookie;
    }
  } else {
    if (req.http.User-Agent ~ "\(compatible; Googlebot-Mobile/2.1; \+http://www.google.com/bot.html\)"
        || (req.http.User-Agent ~ "(Android|iPhone)"
            && req.http.User-Agent ~ "\(compatible.?; Googlebot/2.1.?; \+http://www.google.com/bot.html")
        || (req.http.User-Agent ~ "(iPhone|Windows Phone)"
            && req.http.User-Agent ~ "\(compatible; bingbot/2.0; \+http://www.bing.com/bingbot.htm")
    ) {
      set req.http.X-UA-Device = "mobile-bot";
    } elsif (req.http.User-Agent ~ "(?i)(ads|google|bing|msn|yandex|baidu|ro|career|seznam|)bot"
             || req.http.User-Agent ~ "(?i)(baidu|jike|symantec)spider"
             || req.http.User-Agent ~ "(?i)scanner"
             || req.http.User-Agent ~ "(?i)(web)crawler"
    ) {
      set req.http.X-UA-Device = "bot";
    } elsif (req.http.User-Agent ~ "(?i)ipad") {
      set req.http.X-UA-Device = "tablet-ipad";
    } elsif (req.http.User-Agent ~ "(?i)ip(hone|od)") {
      set req.http.X-UA-Device = "mobile-iphone";
    } elsif (req.http.User-Agent ~ "(?i)android.*(mobile|mini)") {
      /* how do we differ between an android phone and an android tablet? */
      /* http://stackoverflow.com/questions/5341637/how-do-detect-android-tablets-in-general-useragent */
      set req.http.X-UA-Device = "mobile-android";
    }
    elsif (req.http.User-Agent ~ "(?i)android 3") {
      /* https://github.com/varnish/varnish-devicedetect/blob/master/devicedetect.vcl */
      set req.http.X-UA-Device = "tablet-android";
    } elsif (req.http.User-Agent ~ "Opera Mobi") {
      /* Opera Mobile */
      set req.http.X-UA-Device = "mobile-smartphone";
    }
    elsif (req.http.User-Agent ~ "(?i)android") {
      /* May very well give false positives towards android tablets. Suggestions welcome. */
      set req.http.X-UA-Device = "tablet-android";
    } elsif (req.http.User-Agent ~ "PlayBook; U; RIM Tablet") {
      set req.http.X-UA-Device = "tablet-rim";
    } elsif (req.http.User-Agent ~ "hp-tablet.*TouchPad") {
      set req.http.X-UA-Device = "tablet-hp";
    } elsif (req.http.User-Agent ~ "Kindle/3") {
      set req.http.X-UA-Device = "tablet-kindle";
    } elsif (req.http.User-Agent ~ "Touch.+Tablet PC" || req.http.User-Agent ~ "Windows NT [0-9.]+; ARM;" ) {
      set req.http.X-UA-Device = "tablet-microsoft";
    } elsif (req.http.User-Agent ~ "Mobile.+Firefox") {
      set req.http.X-UA-Device = "mobile-firefoxos";
    } elsif (req.http.User-Agent ~ "^HTC"
             || req.http.User-Agent ~ "Fennec"
             || req.http.User-Agent ~ "IEMobile"
             || req.http.User-Agent ~ "BlackBerry"
             || req.http.User-Agent ~ "BB10.*Mobile"
             || req.http.User-Agent ~ "GT-.*Build/GINGERBREAD"
             || req.http.User-Agent ~ "SymbianOS.*AppleWebKit") {
      set req.http.X-UA-Device = "mobile-smartphone";
    }
    elsif (req.http.User-Agent ~ "(?i)symbian"
           || req.http.User-Agent ~ "(?i)^sonyericsson"
           || req.http.User-Agent ~ "(?i)^nokia"
           || req.http.User-Agent ~ "(?i)^samsung"
           || req.http.User-Agent ~ "(?i)^lg"
           || req.http.User-Agent ~ "(?i)bada"
           || req.http.User-Agent ~ "(?i)blazer"
           || req.http.User-Agent ~ "(?i)cellphone"
           || req.http.User-Agent ~ "(?i)iemobile"
           || req.http.User-Agent ~ "(?i)midp-2.0"
           || req.http.User-Agent ~ "(?i)u990"
           || req.http.User-Agent ~ "(?i)netfront"
           || req.http.User-Agent ~ "(?i)opera mini"
           || req.http.User-Agent ~ "(?i)palm"
           || req.http.User-Agent ~ "(?i)nintendo wii"
           || req.http.User-Agent ~ "(?i)playstation portable"
           || req.http.User-Agent ~ "(?i)portalmmm"
           || req.http.User-Agent ~ "(?i)proxinet"
           || req.http.User-Agent ~ "(?i)sonyericsson"
           || req.http.User-Agent ~ "(?i)symbian"
           || req.http.User-Agent ~ "(?i)windows\ ?ce"
           || req.http.User-Agent ~ "(?i)winwap"
           || req.http.User-Agent ~ "(?i)eudoraweb"
           || req.http.User-Agent ~ "(?i)htc"
           || req.http.User-Agent ~ "(?i)240x320"
           || req.http.User-Agent ~ "(?i)avantgo") {
      set req.http.X-UA-Device = "mobile-generic";
    }
  }
}


We were jumping to vcl_error in maintenance mode ignoring the custom error/maint page

    # VCL required to support maintenance mode. Don't maintenance mode admin pages and supporting assets
    if (table.lookup(magentomodule_config, "allow_super_users_during_maint", "0") == "1" &&
        !req.http.Fastly-Client-Ip ~ maint_allowlist &&
        !req.url ~ "^/(index\.php/)?####ADMIN_PATH####/" &&
        !req.url ~ "^/pub/static/") {

        # If we end up here after a restart and there is a ResponseObject it means we got here after error
        # page VCL restart. We shouldn't touch it. Otherwise return a plain 503 error page
        if ( req.restarts > 0 && req.http.ResponseObject ) {
            set req.http.ResponseObject = "970";
        } else {
            error 503 "Maintenance mode";
        }
    }

    # Fixup for Varnish ESI not dealing with https:// absolute URLs well
    if (req.is_esi_subreq && req.url ~ "/https://([^/]+)(/.*)$") {
        set req.http.Host = re.group.1;
        set req.url = re.group.2;
    }

    unset req.http.x-long-cache;

    # We want to force long cache times on any of the versioned assets
    if (req.url.path ~ "^/static/version\d*/") {
       set req.http.x-long-cache = "1";
    }

    # User's Cookie may contain some Magento Vary items we should vary on
    if (req.http.cookie:X-Magento-Vary ) {
        set req.http.X-Magento-Vary = req.http.cookie:X-Magento-Vary;
    } else {
        unset req.http.X-Magento-Vary;
    }

    ############################################################################################################
    # Following code block controls purge by URL. By default we want to protect all URL purges. In general this
    # is addressed by adding Fastly-Purge-Requires-Auth request header in vcl_recv however this runs the risk of
    # exposing API tokens if user attempts to purge non-https URLs. For this reason inside the Magento module
    # we use X-Purge-Token. Unfortunately this breaks purge from the Fastly UI. Therefore in the next code block
    # we check for presence of X-Purge-Token. If it's not present we force the Fastly-Purge-Requires-Auth
    if (req.request == "FASTLYPURGE") {
        # extract token signature and expiration
        if (req.http.X-Purge-Token && req.http.X-Purge-Token ~ "^([^_]+)_(.*)" ) {

            declare local var.X-Exp STRING;
            declare local var.X-Sig STRING;
            /* extract token expiration and signature */
            set var.X-Exp = re.group.1;
            set var.X-Sig = re.group.2;

            /* validate signature */
            if (var.X-Sig == regsub(digest.hmac_sha1(req.service_id, req.url.path var.X-Exp), "^0x", "")) {
            /* check that expiration time has not elapsed */
                if (time.is_after(now, std.integer2time(std.atoi(var.X-Exp)))) {
                    error 410;
                }
            } else {
                error 403;
            }
        } else {
            set req.http.Fastly-Purge-Requires-Auth = "1";
        }
    }

    # set HTTPS header for offloaded TLS
    if (req.http.Fastly-SSL) {
        set req.http.Https = "on";
    }

    if (req.http.Fastly-FF) {
        # disable ESI processing on Origin Shield
        set req.esi = false;
        # Needed for proper handling of stale while revalidated when shielding is involved
        set req.max_stale_while_revalidate = 0s;
    }

    # Make sure we lookup end user geo not shielding. More at https://docs.fastly.com/vcl/geolocation/#using-geographic-variables-with-shielding
    set client.geo.ip_override = req.http.Fastly-Client-IP;

    # geoip lookup
    if (req.url ~ "fastlyCdn/geoip/getaction/") {
        # check if GeoIP has been already processed by client. this normally happens before essential cookies are set.
        if (req.http.cookie:X-Magento-Vary || req.http.cookie:form_key) {
            error 980 "GeoIP already processed";
        } else {
            # append parameter with country code only if it doesn't exist already
            if ( req.url.qs !~ "country_code=" ) {
                set req.url = req.url "?country_code=" if ( req.http.geo_override, req.http.geo_override, client.geo.country_code);
            }
        }
    } else {
        # Per suggestions in https://github.com/sdinteractive/SomethingDigital_PageCacheParams
        # we'll strip out query parameters used in Google AdWords, Mailchimp tracking by default
        # and allow custom parameters to be set. List of parameters is configurable in admin
        set req.http.Magento-Original-URL = req.url;
        # Change the list of ignored parameters by configuring them in the Advanced section
        set req.url = querystring.regfilter(req.url, "^(####QUERY_PARAMETERS####)");
    }

    # Don't allow clients to force a pass
    if (req.restarts == 0) {
        unset req.http.x-pass;
        unset req.http.Rate-Limit;
    }

    # Pass on checkout URLs. Because it's a snippet we want to execute this after backend selection so we handle it
    # in the request condition
    if (!req.http.x-long-cache && req.url ~ "/(catalogsearch|checkout|customer/section/load)") {
        set req.http.x-pass = "1";
    # Pass all admin actions
    # ####ADMIN_PATH#### is replaced with value of frontName from app/etc/env.php
    } else if ( req.url ~ "^/(index\.php/)?####ADMIN_PATH####/" ) {
        set req.http.x-pass = "1";
    } else {
        # Sort the query arguments to increase cache hit ratio with query arguments that
        # may be out od order. In case you want to restore the unsorted URL add a snippet
        # after this one that sets req.url to req.http.Magento-Original-URL
        if ( !req.http.x-pass ) {
            set req.url = boltsort.sort(req.url);
        }
    }

    # static files are always cacheable. remove SSL flag and cookie
    if (req.http.x-long-cache || req.url ~ "^/(pub/)?(media|static)/.*") {
        unset req.http.Https;
        unset req.http.Cookie;
    }

    # VCL required to support maintenance mode. Don't maintenance mode admin pages and supporting assets
    if (table.lookup(magentomodule_config, "allow_super_users_during_maint", "0") == "1" &&
        !req.http.Fastly-Client-Ip ~ maint_allowlist &&
        !req.url ~ "^/(index\.php/)?####ADMIN_PATH####/" &&
        !req.url ~ "^/pub/static/") {
        error 503 "Maintenance mode";
    }

    # Fixup for Varnish ESI not dealing with https:// absolute URLs well
    if (req.is_esi_subreq && req.url ~ "/https://([^/]+)(/.*)$") {
        set req.http.Host = re.group.1;
        set req.url = re.group.2;
    }

    unset req.http.x-long-cache;

    # We want to force long cache times on any of the versioned assets
    if (req.url.path ~ "^/static/version\d*/") {
       set req.http.x-long-cache = "1";
    }

    # User's Cookie may contain some Magento Vary items we should vary on
    if (req.http.cookie:X-Magento-Vary ) {
        set req.http.X-Magento-Vary = req.http.cookie:X-Magento-Vary;
    } else {
        unset req.http.X-Magento-Vary;
    }

    ############################################################################################################
    # Following code block controls purge by URL. By default we want to protect all URL purges. In general this
    # is addressed by adding Fastly-Purge-Requires-Auth request header in vcl_recv however this runs the risk of
    # exposing API tokens if user attempts to purge non-https URLs. For this reason inside the Magento module
    # we use X-Purge-Token. Unfortunately this breaks purge from the Fastly UI. Therefore in the next code block
    # we check for presence of X-Purge-Token. If it's not present we force the Fastly-Purge-Requires-Auth
    if (req.request == "FASTLYPURGE") {
        # extract token signature and expiration
        if (req.http.X-Purge-Token && req.http.X-Purge-Token ~ "^([^_]+)_(.*)" ) {

            declare local var.X-Exp STRING;
            declare local var.X-Sig STRING;
            /* extract token expiration and signature */
            set var.X-Exp = re.group.1;
            set var.X-Sig = re.group.2;

            /* validate signature */
            if (var.X-Sig == regsub(digest.hmac_sha1(req.service_id, req.url.path var.X-Exp), "^0x", "")) {
            /* check that expiration time has not elapsed */
                if (time.is_after(now, std.integer2time(std.atoi(var.X-Exp)))) {
                    error 410;
                }
            } else {
                error 403;
            }
        } else {
            set req.http.Fastly-Purge-Requires-Auth = "1";
        }
    }

    # set HTTPS header for offloaded TLS
    if (req.http.Fastly-SSL) {
        set req.http.Https = "on";
    }

    if (req.http.Fastly-FF) {
        # disable ESI processing on Origin Shield
        set req.esi = false;
        # Needed for proper handling of stale while revalidated when shielding is involved
        set req.max_stale_while_revalidate = 0s;
    }

    # Make sure we lookup end user geo not shielding. More at https://docs.fastly.com/vcl/geolocation/#using-geographic-variables-with-shielding
    set client.geo.ip_override = req.http.Fastly-Client-IP;

    # geoip lookup
    if (req.url ~ "fastlyCdn/geoip/getaction/") {
        # check if GeoIP has been already processed by client. this normally happens before essential cookies are set.
        if (req.http.cookie:X-Magento-Vary || req.http.cookie:form_key) {
            error 980 "GeoIP already processed";
        } else {
            # append parameter with country code only if it doesn't exist already
            if ( req.url.qs !~ "country_code=" ) {
                set req.url = req.url "?country_code=" if ( req.http.geo_override, req.http.geo_override, client.geo.country_code);
            }
        }
    } else {
        # Per suggestions in https://github.com/sdinteractive/SomethingDigital_PageCacheParams
        # we'll strip out query parameters used in Google AdWords, Mailchimp tracking by default
        # and allow custom parameters to be set. List of parameters is configurable in admin
        set req.http.Magento-Original-URL = req.url;
        # Change the list of ignored parameters by configuring them in the Advanced section
        set req.url = querystring.regfilter(req.url, "^(####QUERY_PARAMETERS####)");
    }

    # Don't allow clients to force a pass
    if (req.restarts == 0) {
        unset req.http.x-pass;
        unset req.http.Rate-Limit;
    }

    # Pass on checkout URLs. Because it's a snippet we want to execute this after backend selection so we handle it
    # in the request condition
    if (!req.http.x-long-cache && req.url ~ "/(catalogsearch|checkout|customer/section/load)") {
        set req.http.x-pass = "1";
    # Pass all admin actions
    # ####ADMIN_PATH#### is replaced with value of frontName from app/etc/env.php
    } else if ( req.url ~ "^/(index\.php/)?####ADMIN_PATH####/" ) {
        set req.http.x-pass = "1";
    } else {
        # Sort the query arguments to increase cache hit ratio with query arguments that
        # may be out od order. In case you want to restore the unsorted URL add a snippet
        # after this one that sets req.url to req.http.Magento-Original-URL
        if ( !req.http.x-pass ) {
            set req.url = boltsort.sort(req.url);
        }
    }

    # static files are always cacheable. remove SSL flag and cookie
    if (req.http.x-long-cache || req.url ~ "^/(pub/)?(media|static)/.*") {
        unset req.http.Https;
        unset req.http.Cookie;
    }