Replace custom VCL logic with new Fastly features (#2148)

* Replace custom solution for figuring out whether it is the first time the request has entered the service with the new Fastly provided solution

* use new fastly.ff.visits_this_service instead of custom solution

Co-authored-by: Jake Champion <0fc28a72b6b6b7b90a731ec9d7d2cc16a612eed7@googlemail.com>

sub set_backend {
	# The Fastly macro is inserted before the backend is selected because the
	# macro has the code which defines the values avaiable for req.http.Host
	# but it also contains a default backend which is always set to the EU. I wish we could disable the default backend setting.
	#FASTLY recv

	# Calculate the ideal region to route the request to.
  	declare local var.region STRING;
	if (server.region ~ "(APAC|Asia|North-America|South-America|US-Central|US-East|US-West)") {
		set var.region = "US";
  	} else {
		set var.region = "EU";
  	}

	# Gather the health of the shields and origins.
	declare local var.v3_eu_is_healthy BOOL;
	set req.backend = F_v3_eu;
	set var.v3_eu_is_healthy = req.backend.healthy;

	declare local var.v3_us_is_healthy BOOL;
  	set req.backend = F_v3_us;
  	set var.v3_us_is_healthy = req.backend.healthy;

  	declare local var.shield_eu_is_healthy BOOL;
  	set req.backend = ssl_shield_london_city_uk;
  	set var.shield_eu_is_healthy = req.backend.healthy;

  	declare local var.shield_us_is_healthy BOOL;
  	set req.backend = ssl_shield_dca_dc_us;
  	set var.shield_us_is_healthy = req.backend.healthy;

  	# Set some sort of default, that shouldn't get used.
  	set req.backend = F_v3_eu;

	declare local var.EU_shield_server_name STRING;
	set var.EU_shield_server_name = "LCY";

	declare local var.US_shield_server_name STRING;
	set var.US_shield_server_name = "DCA";

	# Route EU requests to the nearest healthy shield or origin.
  	if (var.region == "EU") {
		if (server.datacenter != var.EU_shield_server_name && fastly.ff.visits_this_service == 0 && req.restarts == 0 && var.shield_eu_is_healthy) {
			set req.backend = ssl_shield_london_city_uk;
		} elseif (var.v3_eu_is_healthy) {
			set req.backend = F_v3_eu;
		} elseif (var.shield_us_is_healthy) {
			set req.backend = ssl_shield_dca_dc_us;
		} elseif (var.v3_us_is_healthy) {
			set req.backend = F_v3_us;
		} else {
			# Everything is on fire... but lets try the origin anyway just in case
			# it's the probes that are wrong
			# set req.backend = F_origin_last_ditch_eu;
		}
  	}

	# Route US requests to the nearest healthy shield or origin.
  	if (var.region == "US") {
		if (server.datacenter != var.US_shield_server_name && fastly.ff.visits_this_service == 0 && req.restarts == 0 && var.shield_us_is_healthy) {
			set req.backend = ssl_shield_dca_dc_us;
		} elseif (var.v3_us_is_healthy) {
			set req.backend = F_v3_us;
		} elseif (var.shield_eu_is_healthy) {
			set req.backend = ssl_shield_london_city_uk;
		} elseif (var.v3_eu_is_healthy) {
			set req.backend = F_v3_eu;
		} else {
			# Everything is on fire... but lets try the origin anyway just in case
			# it's the probes that are wrong
			# set req.backend = F_origin_last_ditch_us;
		}
	}

	# Persist the decision so we can debug the result.
  	set req.http.Debug-Backend = req.backend;
}

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

	if (req.method != "GET" && req.method != "HEAD" && req.method != "OPTIONS" && req.method != "FASTLYPURGE" && req.method != "PURGE") {
		error 911;
	}

	if (req.method == "OPTIONS") {
		error 912;
	}

	# Because the old service had a router which allowed any words between /v2/polyfill. and .js
	if (req.url.path ~ "^/v2/polyfill(\.\w+)(\.\w+)?" && req.url.ext == "js") {
		if (re.group.2) {
			set req.url = "/v2/polyfill.min.js?" req.url.qs;
		} else {
			set req.url = "/v2/polyfill.js?" req.url.qs;
		}
	}

	# Override the v3 defaults with the defaults of v2
	if (req.url ~ "^/v2/polyfill(\.min)?\.js") {
		set req.url = regsub(req.url, "^/v2", "/v3");
		set req.url = querystring.set(req.url, "version", "3.25.1");
		declare local var.unknown STRING;
		set var.unknown = subfield(req.url.qs, "unknown", "&");
		set req.url = querystring.set(req.url, "unknown", if(var.unknown != "", var.unknown, "ignore"));
	}

	if (req.url ~ "^/v3/polyfill(\.min)?\.js") {
		call normalise_querystring_parameters_for_polyfill_bundle;
		# Sort the querystring parameters alphabetically to improve chances of hitting a cached copy.
		# If querystring is empty, remove the ? from the url.
		set req.url = querystring.clean(querystring.sort(req.url));
		call set_backend;
	} else {
		# The request is to an endpoint which doesn't use query parameters, let's remove them to increase our cache-hit-ratio
		set req.url = querystring.remove(req.url);
		call set_backend;
	}

	if (req.backend == ssl_shield_dca_dc_us || req.backend == ssl_shield_london_city_uk) {
		# avoid passing stale content from Shield POP to Edge POP
		set req.max_stale_while_revalidate = 0s;
	} else {
		set req.http.referer_domain = if(req.http.referer ~ "^https?\:\/\/([^\/:?#]+)(?:[\/:?#]|$)", re.group.1, "");
	}
}

sub vcl_hash {
	if (req.http.Fastly-Debug) {
		call breadcrumb_hash;
	}

	# We are not adding req.http.host to the hash because we want https://cdn.polyfill.io and https://polyfill.io to be a single object in the cache.
	# set req.hash += req.http.host;
	set req.hash += req.url;
	# We include return(hash) to stop the function falling through to the default VCL built into varnish, which for vcl_hash will add req.url and req.http.Host to the hash.
	return(hash);
}

sub vcl_miss {
	if (req.http.Fastly-Debug) {
		call breadcrumb_miss;
	}
}

sub vcl_pass {
	if (req.http.Fastly-Debug) {
		call breadcrumb_pass;
	}
}

sub vcl_fetch {
	if (req.http.Fastly-Debug) {
		call breadcrumb_fetch;
	}

	# These header are only required for HTML documents.
	if (beresp.http.Content-Type ~ "text/html") {
		# Enables the cross-site scripting filter built into most modern web browsers.
		set beresp.http.X-XSS-Protection = "1; mode=block";
	}
	# Prevents MIME-sniffing a response away from the declared content type.
	set beresp.http.X-Content-Type-Options = "nosniff";

	# Ensure the site is only served over HTTPS and reduce the chances of someone performing a MITM attack.
	set beresp.http.Strict-Transport-Security = "max-age=31536000; includeSubdomains; preload";

	# The Referrer-Policy header governs which referrer information, sent in the Referer header, should be included with requests made.
	# Send a full URL when performing a same-origin request, but only send the origin of the document for other cases.
	set beresp.http.Referrer-Policy = "origin-when-cross-origin";

	# Enable purging of all objects in the Fastly cache by issuing a purge with the key "polyfill-service".
	if (beresp.http.Surrogate-Key !~ "\bpolyfill-service\b") {
		set beresp.http.Surrogate-Key = if(beresp.http.Surrogate-Key, beresp.http.Surrogate-Key " polyfill-service", "polyfill-service");
	}

	set beresp.http.Timing-Allow-Origin = "*";

	if (req.http.Normalized-User-Agent) {
		set beresp.http.Normalized-User-Agent = req.http.Normalized-User-Agent;
		set beresp.http.Detected-User-Agent = req.http.useragent_parser_family "/"  req.http.useragent_parser_major "." req.http.useragent_parser_minor "." req.http.useragent_parser_patch;
	}
}

sub vcl_deliver {
	if (req.http.Fastly-Debug) {
		call breadcrumb_deliver;
	}

	set req.http.Fastly-Force-Shield = "yes";

	# Allow cross origin GET, HEAD, and OPTIONS requests to be made.
	if (req.http.Origin) {
		set resp.http.Access-Control-Allow-Origin = "*";
		set resp.http.Access-Control-Allow-Methods = "GET,HEAD,OPTIONS";
	}

	if (req.url ~ "^/v3/polyfill(\.min)?\.js" && fastly.ff.visits_this_service == 0 && req.restarts == 0 && req.backend != ssl_shield_dca_dc_us && req.backend != ssl_shield_london_city_uk) {
		# Need to add "Vary: User-Agent" in after vcl_fetch to avoid the
		# "Vary: User-Agent" entering the Varnish cache.
		# We need "Vary: User-Agent" in the browser cache because a browser
		# may update itself to a version which needs different polyfills
		# So we need to have it ignore the browser cached bundle when the user-agent changes.
		set resp.http.Vary = "User-Agent, Accept-Encoding";
	}

	if (resp.status == 304) {
		set resp.http.Age = "0";
	}

	add resp.http.Server-Timing = fastly_info.state {", fastly;desc="Edge time";dur="} time.elapsed.msec;

	if (req.http.Fastly-Debug) {
		set resp.http.Debug-Backend = req.http.Debug-Backend;
		set resp.http.Debug-Host = req.http.Host;
		set resp.http.Debug-Fastly-Restarts = req.restarts;
		set resp.http.Debug-Orig-URL = req.http.Orig-URL;
		set resp.http.Debug-VCL-Route = req.http.X-VCL-Route;
		set resp.http.useragent_parser_family = req.http.useragent_parser_family;
		set resp.http.useragent_parser_major = req.http.useragent_parser_major;
		set resp.http.useragent_parser_minor = req.http.useragent_parser_minor;
		set resp.http.useragent_parser_patch = req.http.useragent_parser_patch;
	} else {
		unset resp.http.Server;
		unset resp.http.Via;
		unset resp.http.X-Cache;
		unset resp.http.X-Cache-Hits;
		unset resp.http.X-Served-By;
		unset resp.http.X-Timer;
		unset resp.http.Fastly-Restarts;
		unset resp.http.X-PreFetch-Pass;
		unset resp.http.X-PreFetch-Miss;
		unset resp.http.X-PostFetch;
	}
}

sub vcl_error {
	if (obj.status == 911) {
		set obj.status = 405;
		set obj.response = "METHOD NOT ALLOWED";
		set obj.http.Content-Type = "text/html; charset=UTF-8";
		set obj.http.Cache-Control = "private, no-store";
		synthetic req.method " METHOD NOT ALLOWED";
		return (deliver);
	}
	if (obj.status == 912) {
		set obj.status = 200;
		set obj.response = "OK";
		set obj.http.Allow = "OPTIONS, GET, HEAD";
		return (deliver);
	}
}

sub set_backend {
	# The Fastly macro is inserted before the backend is selected because the
	# macro has the code which defines the values avaiable for req.http.Host
	# but it also contains a default backend which is always set to the EU. I wish we could disable the default backend setting.
	#FASTLY recv

	# Calculate the ideal region to route the request to.
  	declare local var.region STRING;
	if (server.region ~ "(APAC|Asia|North-America|South-America|US-Central|US-East|US-West)") {
		set var.region = "US";
  	} else {
		set var.region = "EU";
  	}

	# Gather the health of the shields and origins.
	declare local var.v3_eu_is_healthy BOOL;
	set req.backend = F_v3_eu;
	set var.v3_eu_is_healthy = req.backend.healthy;

	declare local var.v3_us_is_healthy BOOL;
  	set req.backend = F_v3_us;
  	set var.v3_us_is_healthy = req.backend.healthy;

  	declare local var.shield_eu_is_healthy BOOL;
  	set req.backend = ssl_shield_london_city_uk;
  	set var.shield_eu_is_healthy = req.backend.healthy;

  	declare local var.shield_us_is_healthy BOOL;
  	set req.backend = ssl_shield_dca_dc_us;
  	set var.shield_us_is_healthy = req.backend.healthy;

  	# Set some sort of default, that shouldn't get used.
  	set req.backend = F_v3_eu;

	declare local var.EU_shield_server_name STRING;
	set var.EU_shield_server_name = "LCY";

	declare local var.US_shield_server_name STRING;
	set var.US_shield_server_name = "DCA";

	# Route EU requests to the nearest healthy shield or origin.
  	if (var.region == "EU") {
		if (server.datacenter != var.EU_shield_server_name && req.http.Request_Came_From_Shield != var.EU_shield_server_name && var.shield_eu_is_healthy) {
			set req.backend = ssl_shield_london_city_uk;
		} elseif (var.v3_eu_is_healthy) {
			set req.backend = F_v3_eu;
		} elseif (var.shield_us_is_healthy) {
			set req.backend = ssl_shield_dca_dc_us;
		} elseif (var.v3_us_is_healthy) {
			set req.backend = F_v3_us;
		} else {
			# Everything is on fire... but lets try the origin anyway just in case
			# it's the probes that are wrong
			# set req.backend = F_origin_last_ditch_eu;
		}
  	}

	# Route US requests to the nearest healthy shield or origin.
  	if (var.region == "US") {
		if (server.datacenter != var.US_shield_server_name && req.http.Request_Came_From_Shield != var.US_shield_server_name && var.shield_us_is_healthy) {
			set req.backend = ssl_shield_dca_dc_us;
		} elseif (var.v3_us_is_healthy) {
			set req.backend = F_v3_us;
		} elseif (var.shield_eu_is_healthy) {
			set req.backend = ssl_shield_london_city_uk;
		} elseif (var.v3_eu_is_healthy) {
			set req.backend = F_v3_eu;
		} else {
			# Everything is on fire... but lets try the origin anyway just in case
			# it's the probes that are wrong
			# set req.backend = F_origin_last_ditch_us;
		}
	}

	# Persist the decision so we can debug the result.
  	set req.http.Debug-Backend = req.backend;
}

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

	if (req.method != "GET" && req.method != "HEAD" && req.method != "OPTIONS" && req.method != "FASTLYPURGE" && req.method != "PURGE") {
		error 911;
	}

	if (req.method == "OPTIONS") {
		error 912;
	}

	# Because the old service had a router which allowed any words between /v2/polyfill. and .js
	if (req.url.path ~ "^/v2/polyfill(\.\w+)(\.\w+)?" && req.url.ext == "js") {
		if (re.group.2) {
			set req.url = "/v2/polyfill.min.js?" req.url.qs;
		} else {
			set req.url = "/v2/polyfill.js?" req.url.qs;
		}
	}

	# Override the v3 defaults with the defaults of v2
	if (req.url ~ "^/v2/polyfill(\.min)?\.js") {
		set req.url = regsub(req.url, "^/v2", "/v3");
		set req.url = querystring.set(req.url, "version", "3.25.1");
		declare local var.unknown STRING;
		set var.unknown = subfield(req.url.qs, "unknown", "&");
		set req.url = querystring.set(req.url, "unknown", if(var.unknown != "", var.unknown, "ignore"));
	}

	if (req.url ~ "^/v3/polyfill(\.min)?\.js") {
		call normalise_querystring_parameters_for_polyfill_bundle;
		# Sort the querystring parameters alphabetically to improve chances of hitting a cached copy.
		# If querystring is empty, remove the ? from the url.
		set req.url = querystring.clean(querystring.sort(req.url));
		call set_backend;
	} else {
		# The request is to an endpoint which doesn't use query parameters, let's remove them to increase our cache-hit-ratio
		set req.url = querystring.remove(req.url);
		call set_backend;
	}

	if (req.backend == ssl_shield_dca_dc_us || req.backend == ssl_shield_london_city_uk) {
		# avoid passing stale content from Shield POP to Edge POP
		set req.max_stale_while_revalidate = 0s;
	} else {
		set req.http.referer_domain = if(req.http.referer ~ "^https?\:\/\/([^\/:?#]+)(?:[\/:?#]|$)", re.group.1, "");
	}
}

sub vcl_hash {
	if (req.http.Fastly-Debug) {
		call breadcrumb_hash;
	}

	# We are not adding req.http.host to the hash because we want https://cdn.polyfill.io and https://polyfill.io to be a single object in the cache.
	# set req.hash += req.http.host;
	set req.hash += req.url;
	# We include return(hash) to stop the function falling through to the default VCL built into varnish, which for vcl_hash will add req.url and req.http.Host to the hash.
	return(hash);
}

sub shielding_header {
	if (req.backend == ssl_shield_dca_dc_us) {
		set req.http.Request_Came_From_Shield = server.datacenter;
	} elsif (req.backend == ssl_shield_london_city_uk) {
		set req.http.Request_Came_From_Shield = server.datacenter;
	}
}

sub vcl_miss {
	if (req.http.Fastly-Debug) {
		call breadcrumb_miss;
	}
	call shielding_header;
}

sub vcl_pass {
	if (req.http.Fastly-Debug) {
		call breadcrumb_pass;
	}
	call shielding_header;
}

sub vcl_fetch {
	set beresp.http.Request_Came_From_Shield = req.http.Request_Came_From_Shield;
	if (req.http.Fastly-Debug) {
		call breadcrumb_fetch;
	}

	# These header are only required for HTML documents.
	if (beresp.http.Content-Type ~ "text/html") {
		# Enables the cross-site scripting filter built into most modern web browsers.
		set beresp.http.X-XSS-Protection = "1; mode=block";
	}
	# Prevents MIME-sniffing a response away from the declared content type.
	set beresp.http.X-Content-Type-Options = "nosniff";

	# Ensure the site is only served over HTTPS and reduce the chances of someone performing a MITM attack.
	set beresp.http.Strict-Transport-Security = "max-age=31536000; includeSubdomains; preload";

	# The Referrer-Policy header governs which referrer information, sent in the Referer header, should be included with requests made.
	# Send a full URL when performing a same-origin request, but only send the origin of the document for other cases.
	set beresp.http.Referrer-Policy = "origin-when-cross-origin";

	# Enable purging of all objects in the Fastly cache by issuing a purge with the key "polyfill-service".
	if (beresp.http.Surrogate-Key !~ "\bpolyfill-service\b") {
		set beresp.http.Surrogate-Key = if(beresp.http.Surrogate-Key, beresp.http.Surrogate-Key " polyfill-service", "polyfill-service");
	}

	set beresp.http.Timing-Allow-Origin = "*";

	if (req.http.Normalized-User-Agent) {
		set beresp.http.Normalized-User-Agent = req.http.Normalized-User-Agent;
		set beresp.http.Detected-User-Agent = req.http.useragent_parser_family "/"  req.http.useragent_parser_major "." req.http.useragent_parser_minor "." req.http.useragent_parser_patch;
	}
}

sub vcl_deliver {
	if (req.http.Fastly-Debug) {
		call breadcrumb_deliver;
	}

	set req.http.Fastly-Force-Shield = "yes";

	# Allow cross origin GET, HEAD, and OPTIONS requests to be made.
	if (req.http.Origin) {
		set resp.http.Access-Control-Allow-Origin = "*";
		set resp.http.Access-Control-Allow-Methods = "GET,HEAD,OPTIONS";
	}

	if (req.url ~ "^/v3/polyfill(\.min)?\.js" && !resp.http.Request_Came_From_Shield && req.backend != ssl_shield_dca_dc_us && req.backend != ssl_shield_london_city_uk) {
		# Need to add "Vary: User-Agent" in after vcl_fetch to avoid the
		# "Vary: User-Agent" entering the Varnish cache.
		# We need "Vary: User-Agent" in the browser cache because a browser
		# may update itself to a version which needs different polyfills
		# So we need to have it ignore the browser cached bundle when the user-agent changes.
		set resp.http.Vary = "User-Agent, Accept-Encoding";
	}

	if (resp.status == 304) {
		set resp.http.Age = "0";
	}

	add resp.http.Server-Timing = fastly_info.state {", fastly;desc="Edge time";dur="} time.elapsed.msec;

	if (req.http.Fastly-Debug) {
		set resp.http.Debug-Backend = req.http.Debug-Backend;
		set resp.http.Debug-Host = req.http.Host;
		set resp.http.Debug-Fastly-Restarts = req.restarts;
		set resp.http.Debug-Orig-URL = req.http.Orig-URL;
		set resp.http.Debug-VCL-Route = req.http.X-VCL-Route;
		set resp.http.useragent_parser_family = req.http.useragent_parser_family;
		set resp.http.useragent_parser_major = req.http.useragent_parser_major;
		set resp.http.useragent_parser_minor = req.http.useragent_parser_minor;
		set resp.http.useragent_parser_patch = req.http.useragent_parser_patch;
	} else {
		unset resp.http.Server;
		unset resp.http.Via;
		unset resp.http.X-Cache;
		unset resp.http.X-Cache-Hits;
		unset resp.http.X-Served-By;
		unset resp.http.X-Timer;
		unset resp.http.Fastly-Restarts;
		unset resp.http.X-PreFetch-Pass;
		unset resp.http.X-PreFetch-Miss;
		unset resp.http.X-PostFetch;
	}
}

sub vcl_error {
	if (obj.status == 911) {
		set obj.status = 405;
		set obj.response = "METHOD NOT ALLOWED";
		set obj.http.Content-Type = "text/html; charset=UTF-8";
		set obj.http.Cache-Control = "private, no-store";
		synthetic req.method " METHOD NOT ALLOWED";
		return (deliver);
	}
	if (obj.status == 912) {
		set obj.status = 200;
		set obj.response = "OK";
		set obj.http.Allow = "OPTIONS, GET, HEAD";
		return (deliver);
	}
}


Compatibility with images served from S3 directly

    /* handle 5XX (or any other unwanted status code) */
    if (beresp.status >= 500 && beresp.status < 600) {

        /* deliver stale if the object is available */
        if (stale.exists) {
            return(deliver_stale);
        }

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

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

        # Set a short TTL for 404's since those can be temporary during the site build/index
        if (beresp.status == 404) {
            set beresp.ttl = 300s;
            set beresp.http.Cache-Control = "max-age=0";
        } else if (req.http.x-long-cache) {
            # Force caching for signed cached assets.
            set beresp.ttl = 31536000s;
            # Add immutable as it avoids IMS and INM revalidations
            set beresp.http.Cache-Control = "max-age=31536000, immutable";
        }
    }

    # Fix Vary Header in some cases. In 99.9% of cases Varying on User-Agent is counterproductive
    # https://www.varnish-cache.org/trac/wiki/VCLExampleFixupVary
    if (beresp.http.Vary ~ "User-Agent") {
        unset beresp.http.Vary:User-Agent;
        if (beresp.http.Vary == "") {
            unset beresp.http.Vary;
        }
    }

    # All the Magento responses should emit X-Esi headers
    if (beresp.http.x-esi) {
        # enable ESI feature for Magento response by default
        esi;
        # Since varnish doesn't compress ESIs we need to hint to the HTTP/2 terminators to
        # compress it
        set beresp.http.x-compress-hint = "on";
    } else {
        # enable gzip for all static content except
        if ( http_status_matches(beresp.status, "200,404") && (beresp.http.content-type ~ "^(application\/x\-javascript|text\/css|text\/html|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)\s*($|;)" || req.url.ext ~ "(?i)(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.Accept-Encoding == "gzip") {
                set beresp.gzip = true;
            }
        }
    }

    # Add Varying on X-Magento-Vary
    if (beresp.http.Content-Type ~ "text/(html|xml)" || req.http.graphql) {
        set beresp.http.Vary:X-Magento-Vary = "";
        set beresp.http.Vary:Https = "";
    }

    # Just in case the Request Setting for x-pass is missing
    if (req.http.x-pass) {
        return (pass);
    }

    if (beresp.http.Cache-Control ~ "private|no-cache|no-store") {
        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;
    }

    # Never cache 302s
    if (beresp.status == 302) {
        return (pass);
    }

    if (beresp.http.x-amz-request-id) {
        # media retrieved from Amazon S3 should be cacheable even with Shielding enabled
    } else if (!beresp.http.Expires && !beresp.http.Surrogate-Control ~ "max-age" && !beresp.http.Cache-Control ~ "(s-maxage|max-age)") {
        # Varnish sets default TTL if none of the headers above are present. If not set we want to make sure we don't cache it
        set beresp.ttl = 0s;
        set beresp.cacheable = false;
    }

    # 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") && !req.http.x-pass ) {
        unset beresp.http.set-cookie;

        # 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 they are image/script or CSS
        if (beresp.http.Content-Type ~ "(image|script|css)") {
            set beresp.http.Surrogate-Key = re.group.1;
        }
    }

    /* handle 5XX (or any other unwanted status code) */
    if (beresp.status >= 500 && beresp.status < 600) {

        /* deliver stale if the object is available */
        if (stale.exists) {
            return(deliver_stale);
        }

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

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

        # Set a short TTL for 404's since those can be temporary during the site build/index
        if (beresp.status == 404) {
            set beresp.ttl = 300s;
            set beresp.http.Cache-Control = "max-age=0";
        } else if (req.http.x-long-cache) {
            # Force caching for signed cached assets.
            set beresp.ttl = 31536000s;
            # Add immutable as it avoids IMS and INM revalidations
            set beresp.http.Cache-Control = "max-age=31536000, immutable";
        }
    }

    # Fix Vary Header in some cases. In 99.9% of cases Varying on User-Agent is counterproductive
    # https://www.varnish-cache.org/trac/wiki/VCLExampleFixupVary
    if (beresp.http.Vary ~ "User-Agent") {
        unset beresp.http.Vary:User-Agent;
        if (beresp.http.Vary == "") {
            unset beresp.http.Vary;
        }
    }

    # All the Magento responses should emit X-Esi headers
    if (beresp.http.x-esi) {
        # enable ESI feature for Magento response by default
        esi;
        # Since varnish doesn't compress ESIs we need to hint to the HTTP/2 terminators to
        # compress it
        set beresp.http.x-compress-hint = "on";
    } else {
        # enable gzip for all static content except
        if ( http_status_matches(beresp.status, "200,404") && (beresp.http.content-type ~ "^(application\/x\-javascript|text\/css|text\/html|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)\s*($|;)" || req.url.ext ~ "(?i)(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.Accept-Encoding == "gzip") {
                set beresp.gzip = true;
            }
        }
    }

    # Add Varying on X-Magento-Vary
    if (beresp.http.Content-Type ~ "text/(html|xml)" || req.http.graphql) {
        set beresp.http.Vary:X-Magento-Vary = "";
        set beresp.http.Vary:Https = "";
    }

    # Just in case the Request Setting for x-pass is missing
    if (req.http.x-pass) {
        return (pass);
    }

    if (beresp.http.Cache-Control ~ "private|no-cache|no-store") {
        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;
    }

    # Never cache 302s
    if (beresp.status == 302) {
        return (pass);
    }

    # Varnish sets default TTL if none of these are present. If not set we want to make sure we don't cache it
    if (!beresp.http.Expires && !beresp.http.Surrogate-Control ~ "max-age" && !beresp.http.Cache-Control ~ "(s-maxage|max-age)") {
        set beresp.ttl = 0s;
        set beresp.cacheable = false;
    }

    # 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") && !req.http.x-pass ) {
        unset beresp.http.set-cookie;

        # 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 they are image/script or CSS
        if (beresp.http.Content-Type ~ "(image|script|css)") {
            set beresp.http.Surrogate-Key = re.group.1;
        }
    }


IT WORKS! :D

IT WORKS! :D

table consumer_secrets {
	"foo": "super_secret"
}

sub vcl_recv {
#FASTLY recv
#DEPLOY recv
	set req.url = boltsort.sort(req.url);
	set req.http.X-Consumer-Key = regsub(req.url, "(?i).*oauth_consumer_key=([^&]*).*", "\1");
	set req.http.X-Consumer-Secret = table.lookup(consumer_secrets, req.http.X-Consumer-Key);
	set req.http.X-Provided-Signature = regsub(req.url, "(?i).*oauth_signature=([^&]*).*", "\1");
	set req.http.X-Parameters = regsub(req.url, ".*\?", "");
	set req.http.X-Parameters = regsub(req.http.X-Parameters, "&oauth_signature=[^&]*", "");

	set req.http.X-Base-String-Uri =
		if(req.http.Fastly-SSL,"https","http")
		"://"
		std.tolower(req.http.host)
		req.url.path;

	# Construct according to https://tools.ietf.org/html/rfc5849#page-18
	set req.http.X-Signature-Base-String =
		req.request
		"&"
		urlencode(req.http.X-Base-String-Uri)
		"&"
		urlencode(req.http.X-Parameters);

	set req.http.X-Calculated-Signature = urlencode(digest.hmac_sha1_base64(
		req.http.X-Consumer-Secret "&", req.http.X-Signature-Base-String));

	if(req.http.X-Provided-Signature != req.http.X-Calculated-Signature) {
		error 401 "Invalid OAuth signature";
	}

	return(lookup);
}

sub vcl_fetch {
#FASTLY fetch
	return(deliver);
}

sub vcl_hit {
#FASTLY hit
	if (!obj.cacheable) {
		return(pass);
	}
	return(deliver);
}

sub vcl_miss {
#FASTLY miss
	return(fetch);
}

sub vcl_deliver{
#FASTLY deliver
	set resp.http.X-Consumer-Key = req.http.X-Consumer-Key;
	set resp.http.X-Consumer-Secret = req.http.X-Consumer-Secret;
	set resp.http.X-Provided-Signature = req.http.X-Provided-Signature;
	set resp.http.X-Calculated-Signature = req.http.X-Calculated-Signature;
	set resp.http.X-Parameters = req.http.X-Parameters;
	set resp.http.X-Base-String-Uri = req.http.X-Base-String-Uri;
	set resp.http.X-Signature-Base-String = req.http.X-Signature-Base-String;
	return(deliver);
}

sub vcl_error {
#FASTLY error
#DEPLOY error
}

sub vcl_pass {
#FASTLY pass
}

table consumer_secrets {
	"foo": "super_secret"
}

sub vcl_recv {
#FASTLY recv
#DEPLOY recv
	set req.url = boltsort.sort(req.url);
	set req.http.X-Consumer-Key = regsub(req.url, ".*\?.*oauth_consumer_key=(.*?)(&|$)", "\1");
	set req.http.X-Consumer-Secret = table.lookup(consumer_secrets, req.http.X-Consumer-Key);
	set req.http.X-Provided-Signature = regsub(req.url, ".*\?.*oauth_signature=(.*?)(&|$)", "\1");
	set req.http.X-Parameters = regsub(req.url, ".*\?", "");
	set req.http.X-Parameters = regsub(req.http.X-Parameters, "&oauth_signature=[^&]*", "");

	set req.http.X-Base-String-Uri =
		if(req.http.Fastly-SSL,"https","http")
		"://"
		std.tolower(req.http.host)
		req.url.path;

	# Construct according to https://tools.ietf.org/html/rfc5849#page-18
	set req.http.X-Signature-Base-String =
		req.request
		"&"
		req.http.X-Base-String-Uri
		"&"
		req.http.X-Parameters;

	set req.http.X-Calculated-Signature = urlencode(digest.hmac_sha1_base64(
		req.http.X-Consumer-Secret "&", req.http.X-Signature-Base-String));

	if(req.http.X-Provided-Signature != req.http.X-Calculated-Signature) {
		error 401 "Invalid OAuth signature";
	}

	return(lookup);
}

sub vcl_fetch {
#FASTLY fetch
	return(deliver);
}

sub vcl_hit {
#FASTLY hit
	if (!obj.cacheable) {
		return(pass);
	}
	return(deliver);
}

sub vcl_miss {
#FASTLY miss
	return(fetch);
}

sub vcl_deliver{
#FASTLY deliver
	set resp.http.X-Consumer-Key = req.http.X-Consumer-Key;
	set resp.http.X-Consumer-Secret = req.http.X-Consumer-Secret;
	set resp.http.X-Provided-Signature = req.http.X-Provided-Signature;
	set resp.http.X-Calculated-Signature = req.http.X-Calculated-Signature;
	set resp.http.X-Parameters = req.http.X-Parameters;
	set resp.http.X-Base-String-Uri = req.http.X-Base-String-Uri;
	set resp.http.X-Signature-Base-String = req.http.X-Signature-Base-String;
	return(deliver);
}

sub vcl_error {
#FASTLY error
#DEPLOY error
}

sub vcl_pass {
#FASTLY pass
}


Some of the maintenance files may be in /pub/error. Excluded them from blocking during maintenance

    # 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";
        }

        if (req.url == "/fastly-io-tester") {
            # IO won't work for PASS requests
            unset req.http.x-pass;
            # IO requires shielding
            set var.fastly_req_do_shield = true;
            set req.hash_always_miss = true;
            set req.http.X-Fastly-Imageopto-Api = "fastly";
        }
    }

    # 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)/") {

        # 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";
        }
    }

    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;

    if ((req.request == "GET" || req.request == "HEAD") && req.url.path ~ "/graphql" && req.url.qs ~ "query=") {
        set req.http.graphql = "1";
    } else {
        unset req.http.graphql;
    }

    # 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;
    }

    # GraphQL special headers handling because this area doesn't rely on X-Magento-Vary cookie
    if (req.http.graphql && !req.http.X-Magento-Cache-Id && req.http.Authorization ~ "^Bearer" ) {
        unset req.http.graphql;
        set req.http.x-pass = "1";
    }

    # 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";
        }

        if (req.url == "/fastly-io-tester") {
            # IO won't work for PASS requests
            unset req.http.x-pass;
            # IO requires shielding
            set var.fastly_req_do_shield = true;
            set req.hash_always_miss = true;
            set req.http.X-Fastly-Imageopto-Api = "fastly";
        }
    }

    # 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";
        }
    }

    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;

    if ((req.request == "GET" || req.request == "HEAD") && req.url.path ~ "/graphql" && req.url.qs ~ "query=") {
        set req.http.graphql = "1";
    } else {
        unset req.http.graphql;
    }

    # 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;
    }

    # GraphQL special headers handling because this area doesn't rely on X-Magento-Vary cookie
    if (req.http.graphql && !req.http.X-Magento-Cache-Id && req.http.Authorization ~ "^Bearer" ) {
        unset req.http.graphql;
        set req.http.x-pass = "1";
    }


Simplify regular expression

Simplify regular expression

    # 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 ~ "/(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;
                }
            }
        }
    }


change of ports

change of ports

#
# Backend VCL file - this should be auto-generated by consul
#

backend http_backend1 {
    .host = "127.0.0.1";
    .port = "81";
}

backend http_backend2 {
    .host = "127.0.0.1";
    .port = "81";
}

backend varnish_backend1 {
  .host = "127.0.0.1";
  .port = "80";
}

backend varnish_backend2 {
  .host = "127.0.0.1";
  .port = "80";
}

sub backend_init {
  ## setup varnish backend hash director
  new varnish_backend = directors.hash();
  varnish_backend.add_backend(varnish_backend1, 1.0);
  varnish_backend.add_backend(varnish_backend2, 1.0);

  ## setup actual backend director, using round-robin
  new httpd_backend = directors.round_robin();
  httpd_backend.add_backend(http_backend1);
  httpd_backend.add_backend(http_backend2);
}

#
# Backend VCL file - this should be auto-generated by consul
#

backend http_backend1 {
    .host = "127.0.0.1";
    .port = "10000";
}

backend http_backend2 {
    .host = "127.0.0.1";
    .port = "10000";
}

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

backend varnish_backend2 {
  .host = "127.0.0.1";
  .port = "8081";
}

sub backend_init {
  ## setup varnish backend hash director
  new varnish_backend = directors.hash();
  varnish_backend.add_backend(varnish_backend1, 1.0);
  varnish_backend.add_backend(varnish_backend2, 1.0);

  ## setup actual backend director, using round-robin
  new httpd_backend = directors.round_robin();
  httpd_backend.add_backend(http_backend1);
  httpd_backend.add_backend(http_backend2);
}


Fix vcl for v4

Fix vcl for v4

vcl 4.0;

backend clbws {
  .host = "ws";
  .port = "9000";
  .max_connections = 100;
  .first_byte_timeout     = 300s;   # How long to wait before we receive a first byte from our backend?
  .connect_timeout        = 5s;     # How long to wait for a backend connection?
  .between_bytes_timeout  = 2s;     # How long to wait between bytes received from our backend?
}

#backend nubws {
#  .host = "nubws";
#  .port = "9002";
#  .max_connections = 100;
#  .first_byte_timeout     = 300s;   # How long to wait before we receive a first byte from our backend?
#  .connect_timeout        = 5s;     # How long to wait for a backend connection?
#  .between_bytes_timeout  = 2s;     # How long to wait between bytes received from our backend?
#}

sub vcl_recv {

  # keep original URL in custom header for the ban lurker process later one in vcl_fetch. We modify the request URL here to match the backend apps
  set req.http.x-api-url = req.url;

  if (req.method == "FLUSH") {
          ban("req.http.host == " + req.http.host + " && req.url == /");
          # Throw a synthetic page so the
          # request won't go to the backend.
          return(synth(200, "Flush added"));
  }
  if (req.method == "BAN") {
          ban("req.http.host == " + req.http.host + " && req.url == " + req.url);
          # Throw a synthetic page so the
          # request won't go to the backend.
          return(synth(200, "Ban added"));
  }

  if ( req.url ~ "^/favicon.ico") {
    return(synth(404, "Not found"));
  }

  if (req.url ~ "^/species/(match|lookup)"){
#    set req.backend_hint = nubws;
    set req.backend_hint = clbws;
  } else {
    set req.backend_hint = clbws;
  }

  # apparently varnish tries to cache POST requests by converting them to GETs :(
  # https://www.varnish-cache.org/forum/topic/235
  # we therefore make sure we only cache GET requests
  if (req.method == "GET") {
    return (hash);
  } else {
    return(pass);
  }
}

sub vcl_backend_response {
  # keep original request url in cache for varnishs ban lurker thread:
  # https://www.varnish-software.com/static/book/Cache_invalidation.html#smart-bans
  # x-api-url is set in vcl_recv BAN
  set beresp.http.x-api-url = req.http.x-api-url;

  # 1 hour for all API calls
  set beresp.ttl = 3600s;

  return (deliver);
}

vcl 4.0;

backend clbws {
  .host = "ws";
  .port = "9000";
  .max_connections = 100;
  .first_byte_timeout     = 300s;   # How long to wait before we receive a first byte from our backend?
  .connect_timeout        = 5s;     # How long to wait for a backend connection?
  .between_bytes_timeout  = 2s;     # How long to wait between bytes received from our backend?
}

#backend nubws {
#  .host = "nubws";
#  .port = "9002";
#  .max_connections = 100;
#  .first_byte_timeout     = 300s;   # How long to wait before we receive a first byte from our backend?
#  .connect_timeout        = 5s;     # How long to wait for a backend connection?
#  .between_bytes_timeout  = 2s;     # How long to wait between bytes received from our backend?
#}

sub vcl_recv {

  # keep original URL in custom header for the ban lurker process later one in vcl_fetch. We modify the request URL here to match the backend apps
  set req.http.x-api-url = req.url;

  # this bans matching objects via regex from being served if older than the ban
  if (req.method == "BAN") {
    ban("obj.http.x-api-url ~ " + req.http.x-ban-url);
    error 200 "Banned";
  }

  # remove whatever host we got, as varnish adds the backend hostname if none exists in the request
  remove req.http.host;

  # remove cookies in weird case they exist
  remove req.http.Cookie;

  if ( req.url ~ "^/favicon.ico") {
    error 404 "Not found";
  }

  if (req.url ~ "^/species/(match|lookup)"){
#    set req.backend = nubws;
    set req.backend = clbws;
  } else {
    set req.backend = clbws;
  }

  # apparently varnish tries to cache POST requests by converting them to GETs :(
  # https://www.varnish-cache.org/forum/topic/235
  # we therefore make sure we only cache GET requests
  if (req.method == "GET") {
    return (lookup);
  } else {
    return(pass);
  }
}

sub vcl_fetch {
  # keep original request url in cache for varnishs ban lurker thread:
  # https://www.varnish-software.com/static/book/Cache_invalidation.html#smart-bans
  # x-api-url is set in vcl_recv BAN
  set beresp.http.x-api-url = req.http.x-api-url;

  #
  # CACHE CONTROL
  #
  # remove no cache headers.
  # Cache-Control: mag-age takes precendence over Last-Modified or ETags:
  # https://www.varnish-cache.org/trac/wiki/VCLExampleLongerCaching
  # http://stackoverflow.com/questions/6451137/etag-attribute-present-but-no-cache-control-present-in-http-header
  # http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
  remove beresp.http.Cache-Control;

  # 1 hour for all API calls
  set beresp.ttl = 3600s;

  return (deliver);
}

sub vcl_error {
  if (obj.status == 301) {
    set obj.http.Location = obj.response;
    set obj.status = 301;
    return (deliver);
  }
}


Fix inability to purge by URL from the Fastly UI

    # 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;

    # Rewrite /static/versionxxxxx URLs. Avoids us having to rewrite on nginx layer
    if (req.url ~ "^/static/version(\d*/)?(.*)$") {
       set req.url = "/static/" + re.group.2 + "?" + re.group.1;
       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 {
            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;
    }

    # 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;
        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;
    }

    # 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
        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;
    }

    # 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;

    # Rewrite /static/versionxxxxx URLs. Avoids us having to rewrite on nginx layer
    if (req.url ~ "^/static/version(\d*/)?(.*)$") {
       set req.url = "/static/" + re.group.2 + "?" + re.group.1;
       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;
    }

    # auth for purging
    if (req.request == "FASTLYPURGE") {
      # extract token signature and expiration
      if (!req.http.X-Purge-Token ~ "^([^_]+)_(.*)" ) {
        error 403;
      }

      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;
      }
    }

    # 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;
    }

    # 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
        set req.http.Magento-Original-URL = req.url;
        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;
    }

    # 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
        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;
    }


fix redirects

fix redirects

sub vcl_recv {
	if (req.http.Host ~ "polyfills.io") {
		# Fastly recommend using the 900-999 range of status codes for custom errors within an application's VCL.
		# Do the canonicalise check before the HTTPS check to avoid a double redirect
		error 901 "Canonicalise";
	}
	if (!req.http.Fastly-SSL) {
		# 801 is a special error code that Fastly uses to Force HTTPS on the request
		error 801 "Redirect to HTTPS";
	}
	if (req.http.Orig-URL ~ "^/v1") {
		error 902 "Redirect to V2";
	}

	if (req.url.path == "/" || req.url.path == "/v2" || req.url.path == "/v2/docs" || req.url.path == "/v2/docs/") {
		error 908;
	}
}

sub vcl_error {
  # Redirect to canonical prod/qa origins
	if (obj.status == 901) {
		set obj.status = 301;
		set obj.response = "Moved Permanently";
		set obj.http.Location = "https://polyfill.io" req.url;
		synthetic {""};
		return (deliver);
	}
	# Redirect to v2
	if (obj.status == 902) {
		set obj.status = 301;
		set obj.response = "Moved Permanently";
		# Remove libVersion and gated query parameters if they exist.
		set req.url = querystring.regfilter(req.url, "^\b(libVersion|gated)\b.*");
		set obj.http.Location = if(req.url.path == "/v1", "/v3/", regsub(req.url, "^/v1", "/v2"));
		set obj.http.Deprecation-Notice = "API version 1 has been decommissioned - see the body of this response for more information.";
		synthetic {"API version 1 has been decommissioned. Your request is being redirected to v2.  The `libVersion` and `gated` query string parameters are no longer supported and if present have been removed from your request.\n\nA deprecation period for v1 existed between August and December 2015, during which time v1 requests were honoured but a deprecation warning was added to output."};
		return (deliver);
	}

	# Redirect to v3
	if (obj.status == 908) {
		set obj.status = 301;
		set obj.http.Location = "/v3/";
		return (deliver);
	}
}

sub vcl_recv {
	if (req.http.Host ~ "polyfills.io") {
		# Fastly recommend using the 900-999 range of status codes for custom errors within an application's VCL.
		# Do the canonicalise check before the HTTPS check to avoid a double redirect
		error 901 "Canonicalise";
	}
	if (!req.http.Fastly-SSL) {
		# 801 is a special error code that Fastly uses to Force HTTPS on the request
		error 801 "Redirect to HTTPS";
	}
	if (req.http.Orig-URL ~ "^/v1") {
		error 902 "Redirect to V2";
	}

	if (req.url.path == "/" || req.url.path == "/v2" || req.url.path == "/v2/docs/") {
		error 908;
	}
}

sub vcl_error {
  # Redirect to canonical prod/qa origins
	if (obj.status == 901) {
		set obj.status = 301;
		set obj.response = "Moved Permanently";
		set obj.http.Location = "https://polyfill.io" req.url;
		synthetic {""};
		return (deliver);
	}
	# Redirect to v2
	if (obj.status == 902) {
		set obj.status = 301;
		set obj.response = "Moved Permanently";
		# Remove libVersion and gated query parameters if they exist.
		set req.url = querystring.regfilter(req.url, "^\b(libVersion|gated)\b.*");
		set obj.http.Location = if(req.url.path == "/v1", "/v3/", regsub(req.url, "^/v1", "/v2"));
		set obj.http.Deprecation-Notice = "API version 1 has been decommissioned - see the body of this response for more information.";
		synthetic {"API version 1 has been decommissioned. Your request is being redirected to v2.  The `libVersion` and `gated` query string parameters are no longer supported and if present have been removed from your request.\n\nA deprecation period for v1 existed between August and December 2015, during which time v1 requests were honoured but a deprecation warning was added to output."};
		return (deliver);
	}

	# Redirect to v3
	if (obj.status == 908) {
		set obj.status = 301;
		set obj.http.Location = "/v3/";
		return (deliver);
	}
}


add default backend cache of 60 minutes

add default backend cache of 60 minutes

sub recv_backend {
  # set backend as httpd round-robin director
  set req.backend_hint = httpd_backend.backend();

  # re-set X-Varnish-Cluster-Hash to differentiate hash key for cache
  set req.http.X-Varnish-Cluster-Hash = "backend";

  return (hash);
}

sub backend_response_backend {
  set beresp.http.X-BE = bereq.xid;

  ## set default cache of 60m
  set beresp.ttl = 60m;

  return(deliver);
}

sub deliver_backend {
  # set responde header X-Varnish-Cluster-Backend (value doesnt really matter)
  set resp.http.X-Varnish-Cluster-Backend = req.xid;

  # add backend varnish server IP as response header just for demonstration purposes!
  set resp.http.X-Varnish-Cluster-Backend-Host = server.hostname;

  # set Age header to X-Cache, on frontend we will remove Age header completly.
  set resp.http.X-Cache = resp.http.Age;

  return(deliver);
}

sub recv_backend {
  # set backend as httpd round-robin director
  set req.backend_hint = httpd_backend.backend();

  # re-set X-Varnish-Cluster-Hash to differentiate hash key for cache
  set req.http.X-Varnish-Cluster-Hash = "backend";

  return (hash);
}

sub backend_response_backend {
  set beresp.http.X-BE = bereq.xid;

  return(deliver);
}

sub deliver_backend {
  # set responde header X-Varnish-Cluster-Backend (value doesnt really matter)
  set resp.http.X-Varnish-Cluster-Backend = req.xid;

  # add backend varnish server IP as response header just for demonstration purposes!
  set resp.http.X-Varnish-Cluster-Backend-Host = server.hostname;

  # set Age header to X-Cache, on frontend we will remove Age header completly.
  set resp.http.X-Cache = resp.http.Age;

  return(deliver);
}


Add comment on how to restore original URL

    # 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;

    # Rewrite /static/versionxxxxx URLs. Avoids us having to rewrite on nginx layer
    if (req.url ~ "^/static/version(\d*/)?(.*)$") {
       set req.url = "/static/" + re.group.2 + "?" + re.group.1;
       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 {
            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;
    }

    # 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;
    }

    # 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
        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;
    }

    # 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;

    # Rewrite /static/versionxxxxx URLs. Avoids us having to rewrite on nginx layer
    if (req.url ~ "^/static/version(\d*/)?(.*)$") {
       set req.url = "/static/" + re.group.2 + "?" + re.group.1;
       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 {
            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;
    }

    # 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;
    }

    # 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
        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;
    }