Varnish MISS on some URL's, but HITS on other - magento2

We are using Magento 2 with Varnish cache
We only get Varnish Cache HIT on very few /catalogsearch/result/ pages, and we really can not figure out why we don't get cache HIT on all /catalogsearch/result/ pages.
Please help us in the right direction :-)
Ex.
we always get HIT on this url
https://www.babygear.dk/catalogsearch/result/?q=bog
We always get MISS on a lot of other search queries
https://www.babygear.dk/catalogsearch/result/?q=black
https://www.babygear.dk/catalogsearch/result/?q=box
https://www.babygear.dk/catalogsearch/result/?q=box
Here is our varnish.vlc
# VCL version 5.0 is not supported so it should be 4.0 even though actually used Varnish version is 5
vcl 4.0;
import std;
# The minimal Varnish version is 5.0
# For SSL offloading, pass the following header in your proxy server or load balancer: 'X-Forwarded-Proto: https'
backend default {
.host = "127.0.0.1";
.port = "8080";
.first_byte_timeout = 600s;
}
acl purge {
"127.0.0.1";
}
sub vcl_recv {
if (req.method == "PURGE") {
if (client.ip !~ purge) {
return (synth(405, "Method not allowed"));
}
# To use the X-Pool header for purging varnish during automated deployments, make sure the X-Pool header
# has been added to the response in your backend server config. This is used, for example, by the
# capistrano-magento2 gem for purging old content from varnish during it's deploy routine.
if (!req.http.X-Magento-Tags-Pattern && !req.http.X-Pool) {
return (synth(400, "X-Magento-Tags-Pattern or X-Pool header required"));
}
if (req.http.X-Magento-Tags-Pattern) {
ban("obj.http.X-Magento-Tags ~ " + req.http.X-Magento-Tags-Pattern);
}
if (req.http.X-Pool) {
ban("obj.http.X-Pool ~ " + req.http.X-Pool);
}
return (synth(200, "Purged"));
}
if (req.method != "GET" &&
req.method != "HEAD" &&
req.method != "PUT" &&
req.method != "POST" &&
req.method != "TRACE" &&
req.method != "OPTIONS" &&
req.method != "DELETE") {
/* Non-RFC2616 or CONNECT which is weird. */
return (pipe);
}
# We only deal with GET and HEAD by default
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
}
# Bypass shopping cart, checkout
if (req.url ~ "/checkout") {
return (pass);
}
# Bypass health check requests
if (req.url ~ "/pub/health_check.php") {
return (pass);
}
# Set initial grace period usage status
set req.http.grace = "none";
# normalize url in case of leading HTTP scheme and domain
set req.url = regsub(req.url, "^http[s]?://", "");
# collect all cookies
std.collect(req.http.Cookie);
# Compression filter. See https://www.varnish-cache.org/trac/wiki/FAQ/Compression
if (req.http.Accept-Encoding) {
if (req.url ~ "\.(jpg|jpeg|png|gif|gz|tgz|bz2|tbz|mp3|ogg|swf|flv)$") {
# No point in compressing these
unset req.http.Accept-Encoding;
} elsif (req.http.Accept-Encoding ~ "gzip") {
set req.http.Accept-Encoding = "gzip";
} elsif (req.http.Accept-Encoding ~ "deflate" && req.http.user-agent !~ "MSIE") {
set req.http.Accept-Encoding = "deflate";
} else {
# unknown algorithm
unset req.http.Accept-Encoding;
}
}
# Remove all marketing get parameters to minimize the cache objects
if (req.url ~ "(\?|&)(gclid|ff|fp|cx|ie|cof|siteurl|zanpid|origin|fbclid|mc_[a-z]+|utm_[a-z]+|_bta_[a-z]+)=") {
set req.url = regsuball(req.url, "(gclid|ff|fp|cx|ie|cof|siteurl|zanpid|origin|fbclid|mc_[a-z]+|utm_[a-z]+|_bta_[a-z]+)=[-_A-z0-9+()%.]+&?", "");
set req.url = regsub(req.url, "[?|&]+$", "");
}
# Static files caching
if (req.url ~ "^/(pub/)?(media|static)/") {
# Static files should not be cached by default
#return (pass);
# But if you use a few locales and don't use CDN you can enable caching static files by commenting previous line (#return (pass);) and uncommenting next 3 lines
unset req.http.Https;
unset req.http.X-Forwarded-Proto;
unset req.http.Cookie;
}
return (hash);
}
sub vcl_hash {
if (req.http.cookie ~ "X-Magento-Vary=") {
hash_data(regsub(req.http.cookie, "^.*?X-Magento-Vary=([^;]+);*.*$", "\1"));
}
# For multi site configurations to not cache each other's content
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
# To make sure http users don't see ssl warning
if (req.http.X-Forwarded-Proto) {
hash_data(req.http.X-Forwarded-Proto);
}
if (req.url ~ "/graphql") {
call process_graphql_headers;
}
}
sub process_graphql_headers {
if (req.http.Store) {
hash_data(req.http.Store);
}
if (req.http.Content-Currency) {
hash_data(req.http.Content-Currency);
}
}
sub vcl_backend_response {
set beresp.grace = 3d;
if (beresp.http.content-type ~ "text") {
set beresp.do_esi = true;
}
if (bereq.url ~ "\.js$" || beresp.http.content-type ~ "text") {
set beresp.do_gzip = true;
}
if (beresp.http.X-Magento-Debug) {
set beresp.http.X-Magento-Cache-Control = beresp.http.Cache-Control;
}
# cache only successfully responses
if (beresp.status != 200) {
set beresp.ttl = 0s;
set beresp.uncacheable = true;
return (deliver);
} elsif (beresp.http.Cache-Control ~ "private") {
set beresp.uncacheable = true;
set beresp.ttl = 44400s;
return (deliver);
}
# validate if we need to cache it and prevent from setting cookie
if (beresp.ttl > 0s && (bereq.method == "GET" || bereq.method == "HEAD")) {
unset beresp.http.set-cookie;
}
# If page is not cacheable then bypass varnish for 2 minutes as Hit-For-Pass
if (beresp.ttl <= 0s ||
beresp.http.Surrogate-control ~ "no-store" ||
(!beresp.http.Surrogate-Control &&
beresp.http.Cache-Control ~ "no-cache|no-store") ||
beresp.http.Vary == "*") {
# Mark as Hit-For-Pass for the next 2 minutes
set beresp.ttl = 120s;
set beresp.uncacheable = true;
}
return (deliver);
}
sub vcl_deliver {
if (resp.http.X-Magento-Debug) {
if (resp.http.x-varnish ~ " ") {
set resp.http.X-Magento-Cache-Debug = "HIT";
set resp.http.Grace = req.http.grace;
} else {
set resp.http.X-Magento-Cache-Debug = "MISS";
}
} #else {
# unset resp.http.Age;
# }
# Not letting browser to cache non-static files.
if (resp.http.Cache-Control !~ "private" && req.url !~ "^/(pub/)?(media|static)/") {
set resp.http.Pragma = "no-cache";
set resp.http.Expires = "-1";
set resp.http.Cache-Control = "no-store, no-cache, must-revalidate, max-age=0";
}
unset resp.http.X-Magento-Debug;
unset resp.http.X-Magento-Tags;
unset resp.http.X-Powered-By;
unset resp.http.Server;
unset resp.http.X-Varnish;
unset resp.http.Via;
unset resp.http.Link;
}
sub vcl_hit {
if (obj.ttl >= 0s) {
# Hit within TTL period
return (deliver);
}
if (std.healthy(req.backend_hint)) {
if (obj.ttl + 3000000s > 0s) {
# Hit after TTL expiration, but within grace period
set req.http.grace = "normal (healthy server)";
return (deliver);
} else {
# Hit after TTL and grace expiration
return (miss);
}
} else {
# server is not healthy, retrieve from cache
set req.http.grace = "unlimited (unhealthy server)";
return (deliver);
}
}

It's a bit tough to judge what's really going on, because there's both a Varnish cache in front of Magento and Cloudflare as the CDN.
A no-cache/no-store Cache-Control value
What I am seeing in general for your searches, is the following Cache-Control value:
cache-control: no-store, no-cache, must-revalidate, max-age=0
Based on this value, Varnish will decide not to cache. In the response headers of must of your search results you will see that Age: 0 is set. This means that Varnish doesn't hold the value in cache.
A cached search result that shouldn't be cacheable
However, weirdly enough https://www.babygear.dk/catalogsearch/result/?q=bog does have a an Age header with a value greater than zero:
age: 38948
This means it's been in cache for 38948 seconds. But really, this shouldn't be happening, because the page is not supposed to be cacheable.
Making search results cacheable:
Please make sure you have a Cache-Control header that allows caching, if you want to cache search results.
Example:
Cache-Control: public, s-maxage=3600
Debugging using Varnishlog
If you really want to know what happens in behind the scenes in Varnish, you can perform some debugging using the varnishlog binary.
You could run the following command to get debug output:
varnishlog -g request -q "ReqUrl eq '/catalogsearch/result/\?q=bog'"
This will print some very verbose logs on how Varnish treats the URL that causes the hit. You can add this output to your question, and I can try to examine what's going on.
FYI: I wrote a very detailed blog post about varnishlog a couple of years ago. Please go to https://feryn.eu/blog/varnishlog-measure-varnish-cache-performance/ to have a look, and to learn.
What's Cloudflare doing?
All that being said, I have no clue what the impact of Cloudflare is on the cacheability of the website. The varnishlog output will give us some insight, but if those results diverge from reality, Cloudflare is probably getting in our way.
Keep this in mind while debugging.

Inside vcl_recv Add
# Bypass search requests
if (req.url ~ "/catalogsearch") {
return (pass);
}

check .htaccess file for Cache-Control setting.

Related

Can we perform mapping or do a lookup of string or KV pair in Varnish `sub vcl_recv`

I am trying to route the traffic/connections to backend director based on url parameters which are passed on incoming request.
Here is my current config
sub vcl_recv {
if (req.url ~ "sto") {
set req.backend_hint = endpoint1.backend();
}
}
Currently, above config does static mapping/check for sto. I would like to do a dynamic lookup from map/hash/KV file and do condition check with url contents.
Here is how I do it in haproxy
use_backend %[path,map_sub(/etc/haproxy/GET.map)]
#>cat GET.map
# url paramter backend location/name
/sgtier get_backend1
/sto get_backend2
Config file
vcl 4.0;
import directors;
backend sg_gw1 {
.host = "1.8.4.4";
.port = "1043";
}
backend tss_gw1 {
.host = "1.8.13.2";
.port = "8080";
}
sub vcl_recv {
if (req.url ~ "sto") {
set req.backend_hint = sg_gw1;
}
elsif (req.url ~ "common") {
set req.backend_hint = tss_gw1;
}
}
Dynamic backends are supported in Varnish Enterprise. See https://docs.varnish-software.com/varnish-cache-plus/vmods/goto/ for more information.
An open source alternative for dynamic backends can be found on https://code.uplex.de/uplex-varnish/libvmod-backend_dyn

How to fix unterminated short string error in Varnish VCL while configuring Fastly CDN

I am trying to setup token based authentication on Fastly CDN with Varnish VCL and using this sample VCL snippet to generate and validate JWT tokens here -
sub vcl_recv {
#FASTLY recv
if (req.request != "HEAD" && req.request != "GET" && req.request != "FASTLYPURGE") {
return(pass);
}
// Generate synth
if(req.url ~ "generate") {
error 901;
}
// Validate token
if(req.url ~ "validate") {
// Ensure token exists and parse into regex
if (req.http.X-JWT !~ "^([a-zA-Z0-9\-_]+)?\.([a-zA-Z0-9\-_]+)?\.([a-zA-Z0-9\-_]+)?$") {
// Forbidden
error 403 "Forbidden";
}
// Extract token header, payload and signature
set req.http.X-JWT-Header = re.group.1;
set req.http.X-JWT-Payload = re.group.2;
set req.http.X-JWT-Signature = digest.base64url_nopad_decode(re.group.3);
set req.http.X-JWT-Valid-Signature = digest.hmac_sha256("SupSecretStr",
req.http.X-JWT-Header "." req.http.X-JWT-Payload);
// Validate signature
if(digest.secure_is_equal(req.http.X-JWT-Signature, req.http.X-JWT-Valid-Signature)) {
// Decode payload
set req.http.X-JWT-Payload = digest.base64url_nopad_decode(req.http.X-JWT-Payload);
set req.http.X-JWT-Expires = regsub(req.http.X-JWT-Payload, {"^.*?"exp"\s*?:\s*?([0-9]+).*?$"}, "\1");
// Validate expiration
if (time.is_after(now, std.integer2time(std.atoi(req.http.X-JWT-Expires)))) {
// Unauthorized
synthetic {"{"sign":""} req.http.X-JWT-Signature {"","header":""} req.http.X-JWT-Header {"","payload":""} req.http.X-JWT-Payload {"","valid": ""} req.http.X-JWT-Valid-Signature {""}"};
return(deliver);
}
// OK
synthetic {"{"header2":""} req.http.X-JWT-Header {"","payload":""} req.http.X-JWT-Payload {"","sign":""} req.http.X-JWT-Signature {"","valid": ""} req.http.X-JWT-Valid-Signature {""}"};
return(deliver);
} else {
// Forbidden
synthetic {"{"header3":""} req.http.X-JWT-Header {"","payload":""} req.http.X-JWT-Payload {"","sign":""} req.http.X-JWT-Signature {"","valid": ""} req.http.X-JWT-Valid-Signature {""}"};
return(deliver);
}
}
return(lookup);
}
sub vcl_error {
#FASTLY error
// Generate JWT token
if (obj.status == 901) {
set obj.status = 200;
set obj.response = "OK";
set obj.http.Content-Type = "application/json";
set obj.http.X-UUID = randomstr(8, "0123456789abcdef") "-" randomstr(4, "0123456789abcdef") "-4" randomstr(3, "0123456789abcdef") "-" randomstr(1, "89ab") randomstr(3, "0123456789abcdef") "-" randomstr(12, "0123456789abcdef");
set obj.http.X-JWT-Issued = now.sec;
set obj.http.X-JWT-Expires = strftime({"%s"}, time.add(now, 3600s));
set obj.http.X-JWT-Header = digest.base64url_nopad({"{"alg":"HS256","typ":"JWT""}{"}"});
set obj.http.X-JWT-Payload = digest.base64url_nopad({"{"sub":""} obj.http.X-UUID {"","exp":"} obj.http.X-JWT-Expires {","iat":"} obj.http.X-JWT-Issued {","iss":"Fastly""}{"}"});
set obj.http.X-JWT-Signature = digest.base64url_nopad(digest.hmac_sha256("SupSecretStr", obj.http.X-JWT-Header "." obj.http.X-JWT-Payload));
set obj.http.X-JWT = obj.http.X-JWT-Header "." obj.http.X-JWT-Payload "." obj.http.X-JWT-Signature;
unset obj.http.X-UUID;
unset obj.http.X-JWT-Issued;
unset obj.http.X-JWT-Expires;
unset obj.http.X-JWT-Header;
unset obj.http.X-JWT-payload;
unset obj.http.X-JWT-Signature;
synthetic {"{"payload":""} obj.http.X-JWT-Payload {"","header":""} obj.http.X-JWT-Header {"","sign":""} obj.http.X-JWT-Signatre {"","token": ""} obj.http.X-JWT {""}"};
return(deliver);
}
// Valid token
if (obj.status == 902) {
set obj.status = 200;
set obj.response = "OK";
set obj.http.Content-Type = "application/json";
synthetic {"{ "token": ""} req.http.X-JWT {"" }"};
return(deliver);
}
}
Now, when I am trying to compile this it returns -
Syntax error: Unterminated _short-string_
at: (input Line 106 Pos 197)
synthetic {"{"sign":""} req.http.X-JWT-Signature {"","header":""} req.http.X-JWT-Header {"","payload":""} req.http.X-JWT-Payload {"","valid": ""} req.http.X-JWT-Valid-Signature {""}"};
Looks like i am not somehow escaping the values correctly here during the synthetic block.
The only reason I am trying to do add this synthetic block in the vcl_recv subroutine is because I want to test how the digest is generating the JWT token and validating it and with that I wanted to create similar JWT tokens on server side in Node.Js so i was trying to output the different intermediate parts of the token for debugging.
I am not quite familiar with the Varnish syntax and semantics but still I looked for help finding any docs regarding this schedule subroutine but found none so far.
So, can anyone help out in how to fix this and have the vcl_recv, vcl_error interpolate different intermediate values in the json response.
I tried to use some of the Node.Js based base64 url decode libraries to decode the returned token parts and was able to decode the header and payload part but the signature part I am not able to generate from the Node.Js. So, can anyone suggest what is the equivalent of base64url_nopad() in node.js or any javascript libraries ?
For the hmac_256 encryption part we are trying to use the crypto library and creating an hmac like crypto.createHmac('sha256', 'SupSecretStr').update().digest('hex'); but all base64 encode url libraries in js i think return padded urls which is why the base64 encoded part of this hmac 256 digest doesn't match the one generated with varnish
My syntax coloring tool is telling me pretty much the same thing as the error message: you screwed up your quotes :-)
Your last block {""}"}; is opening quotes ({"), immediately closing them ("}), then you are opening simple quotes " and the newline arrives before you close them.
To fix, just put a space between after the final quote of the json: {"" }"};

NGINX Config: How to redirect array of URLs to home page

I would like an array of URLs, let's say ['/about','/supported-software', '/the-team', ...] to simply redirect to /.
Do I need to write multiple location { } blocks?
I'm new to NGINX configuration so any guidance would be very much appreciated!
If the array entries are exact matches, then the following locations should give you the best performance:
location = /about { return 301 $scheme://$host; }
location = /supported-software { return 301 $scheme://$host; }
location = /the-team { return 301 $scheme://$host; }
# ... or put these in an included file
If they are not exact matches, a map may be better:
map $uri $send_home {
~^/about/? 1;
~^/supported-software/? 1;
~^/the-team/? 1;
# ... or put these in an included file
}
server {
# ...
if ($send_home) {
return 301 $scheme://$host;
}
# ...
}
The map would allow more flexible redirects like:
/about
/about/
/about/stuff

Why do I get an MSPL exception "ProxyRequest only valid for sipRequest"

I'm writing a Lync MSPL application using a manifest and a windows service. In my manifest.am I have the following code:
<?xml version="1.0"?>
<r:applicationManifest
r:appUri="http://www.company.no/LyncServerFilter"
xmlns:r="http://schemas.microsoft.com/lcs/2006/05">
<r:requestFilter methodNames="ALL"
strictRoute="true"
domainSupported="false"/>
<r:responseFilter reasonCodes="ALL"/>
<r:proxyByDefault action="true" />
<r:allowRegistrationBeforeUserServices action="true" />
<r:splScript>
<![CDATA[
callId = GetHeaderValues("Call-ID");
cseq = GetHeaderValues("CSeq");
content = "";
sstate = GetHeaderValues("subscription-state");
xevent = GetHeaderValues("Event");
xdir = GetHeaderValues("Direction");
xexp = GetHeaderValues("Session-Expires");
referto = GetHeaderValues("Refer-To");
if (sipRequest)
{
if (sipRequest.Method == "INVITE") {
if (ContainsString(sipRequest.Content, "m=audio", true)) {
content = "audio";
}
else if (ContainsString(sipRequest.Content, "m=video", true)) {
content = "video";
}
else if (ContainsString(sipRequest.Content, "m=message", true)) {
content = "message";
}
else if (ContainsString(sipRequest.Content, "m=application", true)) {
content = "application";
}
else {
content = "unknown";
}
}
else if (sipRequest.Method == "NOTIFY" || sipRequest.Method == "BENOTIFY") {
content = sipRequest.Content;
}
DispatchNotification("OnRequest", sipRequest.Method, sipMessage.From, sipMessage.To, callId, cseq, content, xdir, xevent, sstate, xexp, referto);
if (sipRequest) {
ProxyRequest();
}
}
else if(sipResponse) {
DispatchNotification("OnResponse", sipResponse.StatusCode, sipResponse.StatusReasonPhrase, sipMessage.From, sipMessage.To, callId, cseq, content, xdir, xevent, sstate, xexp, referto);
ProxyResponse();
}
]]></r:splScript>
</r:applicationManifest>
I'm getting the following errormessage in Eventlog on Lync Front End server:
Lync Server application MSPL script execution aborted because of an error
Application Uri at 'http://www.company.no/LyncServerFilter', at line 60
Error: 0x80070057 - The parameter is incorrect
Additional information: ProxyRequest only valid for sipRequest
Line 60 is where I call ProxyRequest:
if (sipRequest) {
ProxyRequest();
}
Questions:
Why does the errormessage say that ProxyRequest is only valid for a sipRequest? I'm checking that it is a sipMessage right?
Can I remove my call to ProxyRequest() since I have set proxyByDefault=true? Does the DistpathNotification-method "handle" the method (swallow it), or will the message be proxied by default? The code "works" when I remove the call to ProxyRequest(), but I'm not sure what the consequences are...
The ProxyRequest method takes a argument of the uri, which is why you are getting the compile error message.
So you should be calling it like:
ProxyRequest(""); // send to the URI specified in the request itself
Removing it effectivity does the same thing as per your proxyByDefault setting being set to true:
If true, the server automatically proxies any messages that are not handled by the application. If false, the message is dropped and applications that follow this application in the application execution order will not receive it. The default value is true.
As a side-note, you can use compilespl.exe, which comes as part of the Lync Server SDK to verify that your MSPL script is correct before trying to start it on the lync server.
Check out this link in the 'Compile the MSPL application separately' section.

Set Nginx redirect rule priority

I have nginx+php-fpm and some rewrite rules for sef-urls.
The problem is that all my custom redirects/rewrites are ignored and request going to php script instead redirecting.
Part for sef-links:
if ($request_filename !~ ".(png|gif|ico|swf|jpe?g|js|css)$"){
set $rule_0 1$rule_0;
}
if (!-f $request_filename){
set $rule_0 2$rule_0;
}
if (!-d $request_filename){
set $rule_0 3$rule_0;
}
if ($rule_0 = "321"){
rewrite /. /index.php?sef_rewrite=1 last;
}
And I want to do that redirect:
location = /first.html {
return 301 /second.html;
}
You need to thing in terms of nginx's locations and rules. Then it will be much easier to write proper config. Try this one:
location / {
# replacement for last three `if`s
try_files $uri $uri/ /index.php?sef_rewrite=1;
}
# here is you redirect
location = /first.html {
return 301 /second.html;
}
# this is replacement for first `if`
location ~ \.(png|gif|ico|swf|jpe?g|js|css)$ {
# serve static files
}
# I'm sure you have this block somewhere
location ~ \.php$ {
# serve php
}
Also these articles worth to read them:
http://wiki.nginx.org/Pitfalls
http://wiki.nginx.org/IfIsEvil