Using varnish 4 to cache different content of same request from multiple servers. It looks like it caches the first request from one server and keeps giving the same content for every subsequent request.
doing curl gives response with two caches and different age.
Are there any factors like load or anything else for stickiness behaviour?
Used Jmeter and apache benchmark with load but still got the same behaviour.
Is my vcl_hash is good? Want to save the object with hash combination of url and ip of backend server.
Atleast in my case, looks like after the ttl of the cache object, varnish is caching from second server and returns the same until ttl is done. But this is not what we expect it to behave?
am I missing anything?
using round robin and hash_data. below is my config.vcl
backend s1{
.host = "190.120.90.1";
}
backend s2{
.host = "190.120.90.2";
}
sub vcl_init {
new vms = directors.round_robin();
vms.add_backend(s1);
vms.add_backend(s2);
}
sub vcl_recv {
set req.backend_hint = vms.backend();
}
sub vcl_hash {
hash_data(req.url);
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
return(lookup);
}
First thing to consider is that you are going to have the backend ip only after the object has been fetched from it. So you are not able to use that ip on your hash method because vcl_hash occurs before fetch.
Second is about the round robin. It takes place only when Varnish is fetching the objects, hence it will not take place when an object has already been cached.
To answer your question precisely it is needed to know why your application delivers different content for the same request. How do you indicate which backend is being requested if the request is always the same? There must be something like a cookie, a header or the origin IP of the request that should dictate which one must respond to that request.
Knowing that it is possible to set the specific backend and use it in your vcl_hash. For example purposes, let's assume that you want to set your backends based on the presence of a header named backend_choice:
sub vcl_recv {
if (req.http.backends_choice == "s1") {
set req.backend_hint = s1;
# If the header is not "s1" or does not exist
} else {
set req.backend_hint = s2;
}
...
}
sub vcl_hash {
hash_data(req.url);
if (req.http.host) {
hash_data(req.http.host);
} else {
hash_data(server.ip);
}
# We use the selected backend to hash the object
hash_data(req.backend_hint);
return(lookup);
}
Hope this answer address your needs. If there was something that I missed, feel free to comment or add to your question and I'll be glad to add some info to my answer.
Related
Let's say we want to provide a unique CSRF token for each response cached by Varnish.
Is there a way hook Varnish output so that we can change a specific part of the content programmatically?
Varnish Cache solution with ESI & vmod_digest
In the open source version of Varnish, you can use Edge Side Includes for this.
You can put the following place holder in your output:
<esi:include src="/csrf-token-esi" />
This ESI tag will be parsed by Varnish and the /csrf-token-esi endpoint can be intercepted by Varnish and changed.
Be sure to return a Surrogate-Control: Varnish=ESI/1.0 response header on the page that contains the ESI tag. This header will force Varnish to parse the ESI tag.
Via synthetic responses, we can change the response body of /csrf-token-esi.
Here's an example where we use vmod_digest to create an HMAC signature as the CSRF token:
vcl 4.1;
import digest;
backend default {
.host = "localhost";
.port = "8080";
}
sub vcl_recv {
set req.http.Surrogate-Capability = "Varnish=ESI/1.0";
if(req.url == "/csrf-token-esi") {
return(synth(777,digest.hmac_sha256("secret-key", now)));
}
}
sub vcl_backend_response {
if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
unset beresp.http.Surrogate-Control;
set beresp.do_esi = true;
}
}
sub vcl_synth {
if(resp.status == 777) {
set resp.body = resp.reason;
set resp.http.csrf-token = resp.reason;
set resp.status = 200;
return(deliver);
}
}
FYI: you can download the vmod_digest source from https://github.com/varnish/libvmod-digest. You need to compile this from source or use another mechanism to create a unique value.
The value of the CSRF token is generated by the following line of code:
return(synth(777,digest.hmac_sha256("secret-key", now)));
The secret-key is the HMAC's signing key and now returns the current timestamp in Varnish which is the value that will be signed.
There are plenty of other ways to generate a CSRF token. vmod_digest also has other ways to generate unique output. Just remember that the 2nd argument of the synth() function is the response body and effectively the value of the CSRF token.
Varnish Enterprise solution with vmod_edgestash & vmod_crypto
In Varnish Enterprise all the necessary VMODs are packaged, so no need to compile them from source. The vmod_edgestash and vmod_crypto VMODs are enterprise VMODs.
The solution consists of adding a {{csrf}} placeholder in your response. The format of this placeholder is Mustache syntax and is parsed by vmod_edgestash.
Unlike the Varnish Cache solution, no extra roundtrips and request interception are required. Varnish Enterprise will cache the page that includes the placeholder and will customize the CSRF token upon delivery.
Here's the VCL code:
vcl 4.1;
import crypto;
import edgestash;
backend default {
.host = "localhost";
.port = "8080";
}
sub vcl_backend_response{
if (beresp.http.Content-Type ~ "html") {
edgestash.parse_response();
}
}
sub vcl_deliver {
if (edgestash.is_edgestash()) {
set req.http.csrf = crypto.hex_encode(crypto.urandom(16));
edgestash.add_json({"
{"csrf":""} + req.http.csrf + {""
}
"});
edgestash.execute();
}
}
In this case we're using crypto.hex_encode(crypto.urandom(16)) to generate a unique value. But again, you can use any function that vmod_crypto provides to generate this unique value.
See https://docs.varnish-software.com/varnish-cache-plus/vmods/edgestash/ for more information about vmod_edgestash and https://docs.varnish-software.com/varnish-cache-plus/vmods/total-encryption/ for more information about vmod_crypto.
I have a question regarding a small issue that I'm having. I've created a widget that will live on the Service Portal to allow an admin to Accept or Reject requests.
The data for the widget is pulling from the Approvals (approval_approver) table. Under my GlideRecord, I have a query that checks for the state as requested. (Ex. addQuery('state', 'requested'))
To narrow down the search, I tried entering addQuery('sys_id', current.sys_id). When I use this query, my script breaks and I get an error on the Service Portal end.
Here's a sample of the GlideRecord script I've written to Accept.
[//Accept Request
if(input && input.action=="acceptApproval") {
var inRec1 = new GlideRecord('sysapproval_approver');
inRec1.addQuery('state', 'requested');
//inRec1.get('sys_id', current.sys_id);
inRec1.query();
if(inRec1.next()) {
inRec1.setValue('state', 'Approved');
inRec1.setValue('approver', gs.getUserID());
gs.addInfoMessage("Accept Approval Processed");
inRec1.update();
}
}][1]
I've research the web, tried using $sp.getParameter() as a work-around and no change.
I would really appreciate any help or insight on what I can do different to get script to work and filter the right records.
If I understand your question correctly, you are asking how to get the sysId of the sysapproval_approver record from the client-side in a widget.
Unless you have defined current elsewhere in your server script, current is undefined. Secondly, $sp.getParameter() is used to retrieve URL parameters. So unless you've included the sysId as a URL parameter, that will not get you what you are looking for.
One pattern that I've used is to pass an object to the client after the initial query that gets the list of requests.
When you're ready to send input to the server from the client, you can add relevant information to the input object. See the simplified example below. For the sake of brevity, the code below does not include error handling.
// Client-side function
approveRequest = function(sysId) {
$scope.server.get({
action: "requestApproval",
sysId: sysId
})
.then(function(response) {
console.log("Request approved");
});
};
// Server-side
var requestGr = new GlideRecord();
requestGr.addQuery("SOME_QUERY");
requestGr.query(); // Retrieve initial list of requests to display in the template
data.requests = []; // Add array of requests to data object to be passed to the client via the controller
while(requestsGr.next()) {
data.requests.push({
"number": requestsGr.getValue("number");
"state" : requestsGr.getValue("state");
"sysId" : requestsGr.getValue("sys_id");
});
}
if(input && input.action=="acceptApproval") {
var sysapprovalGr = new GlideRecord('sysapproval_approver');
if(sysapprovalGr.get(input.sysId)) {
sysapprovalGr.setValue('state', 'Approved');
sysapprovalGr.setValue('approver', gs.getUserID());
sysapprovalGr.update();
gs.addInfoMessage("Accept Approval Processed");
}
...
Trying to check if both request headers with value or without value i should allow the incoming connection. So i have written a script for that.
But it is not working when both headers with empty value.
It is responding with error code 415
location / {
set $test "00";
if ($http_x_token){
set $test "1";
}
if ($http_api_version){
set $test "${test}1";
}
if ($test = "00"){
return 415;
break;
}
proxy_pass http://127.0.0.1:1234;
}
Anything i am missing in this script?
An empty header field-value is not valid per RFC7230.
https://www.rfc-editor.org/rfc/rfc7230#section-3.2
The field-value is not optional and must be specified.
So nginx doesn't distinguish missed and empty header.
PS: break directive doesn't make sense in code snippet below:
if ($test = "00"){
return 415;
break;
}
https://nginx.ru/en/docs/http/ngx_http_rewrite_module.html#return
return stops processing and returns the specified code to a client.
How Multi-Language Magento store work with Varnish.
Is there any configuration available in varnish,so we can create cache base on cookies?
If you don't mind the languages being at different urls, Turpentine can handle this for you: https://github.com/nexcess/magento-turpentine/issues/36
If you want them to behave as they do out of the box, lets keep going.
You have to modify how varnish generates the has in your VCL
Reference: https://www.varnish-cache.org/trac/wiki/VCLExampleCachingLoggedInUsers
We would modify this to also take into account the store cookie that Magento sets based on the language selector. (Following the behavior here: http://demo.magentocommerce.com) Unfortunately this gets tricky as Varnish tends to either not pass cookies back to the server or not cache things when it sees cookies flying around
This would have Varnish cache based on the value of the cookie as well as the default url and host:
sub vcl_hash {
hash_data(req.url);
hash_data(req.http.host);
if (req.http.Cookie ~ "(?:^|;\s*)(?:store=(.*?))(?:;|$)"){
hash_data(regsub(req.http.Cookie, "(?:^|;\s*)(?:store=(.*?))(?:;|$)"));
}
return (hash);
}
But, with this method you might have to tweak the rest of your VCL to cache the page properly AND send the cookies back to the server
Another option is to use the cookie to vary caching on an arbitrary header, lets call it X-Mage-Lang:
sub vcl_fetch {
#can do this better with regex
if (req.http.Cookie ~ "(?:^|;\s*)(?:store=(.*?))(?:;|$)"){
if (!beresp.http.Vary) { # no Vary at all
set beresp.http.Vary = "X-Mage-Lang";
} elseif (beresp.http.Vary !~ "X-Mage-Lang") { # add to existing Vary
set beresp.http.Vary = beresp.http.Vary + ", X-Mage-Lang";
}
}
# comment this out if you don't want the client to know your classification
set beresp.http.X-Mage-Lang = regsub(req.http.Cookie, "(?:^|;\s*)(?:store=(.*?))(?:;|$)");
}
This pattern is also used for device detection with varnish: https://github.com/varnish/varnish-devicedetect/blob/master/INSTALL.rst
Then, you would have to extend Mage_Core_Model_App to use this header instead of the 'store' cookie. In Magento CE 1.7 its _checkCookieStore :
protected function _checkCookieStore($type)
{
if (!$this->getCookie()->get()) {
return $this;
}
$store = $this->getCookie()->get(Mage_Core_Model_Store::COOKIE_NAME);
if ($store && isset($this->_stores[$store])
&& $this->_stores[$store]->getId()
&& $this->_stores[$store]->getIsActive()) {
if ($type == 'website'
&& $this->_stores[$store]->getWebsiteId() == $this->_stores[$this->_currentStore]->getWebsiteId()) {
$this->_currentStore = $store;
}
if ($type == 'group'
&& $this->_stores[$store]->getGroupId() == $this->_stores[$this->_currentStore]->getGroupId()) {
$this->_currentStore = $store;
}
if ($type == 'store') {
$this->_currentStore = $store;
}
}
return $this;
}
You would set the current store on $_SERVER['X-Mage-Lang'] instead of the cookie
Add Following lines in Varnish Config,
if(beresp.http.Set-Cookie) {
return (hit_for_pass);
}
I have the follow problem. I need to create a JS widget and set it on one blog, for example any blog from blogger.com. YOu can select there a box for javascript and I will post the JS in this box.
The problem what I have and don't know how to do this is, that the script should do an ajax polling for exmaple for 60 seconds. But how to execute an ajax call, when the host is not the same linke the host, where the JS is includet?
For example the easiest way to explai is: There is a search box and when enayone searches for anythign, then the JS script should streaming the results for 60 seconds from the server what I have set in the script and is different as the host, where the JS is includet, without to become a problem with the JS restriction for hosts.
Or for example a chat client, where the client is hosted on one other host and the server on another.
Can anyone tell me an idea, or send me an example how to do this?
Thanks
Nik
Well with this example is it possible but without JSONP?
function asyncreq(url) {
var xmlhttp = false;
try {
xmlhttp = new XMLHttpRequest();
} catch (trymicrosoft) {
try {
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (othermicrosoft) {
try {
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (failed) {
xmlhttp = false;
}
}
}
if (xmlhttp){
try {
xmlhttp.open("GET", url);
xmlhttp.onreadystatechange=function() {
document.getElementById('mydiv').innerHTML = xmlhttp.responseText;
}
xmlhttp.send(null);
}
catch (failed) {
xmlhttp = false;
}
}
}
If you send the response in chunks, then everything is fine. But here is the call in ajax again. And when I use it in a different host, then I can't call the url because of the same-origin policy.
Is there another way?
I found a very interesting example here.
Take a look at the bottom, there is a job search box. If you investigate a litte bit, then you will see there is a usage of a class RSL() which is doing the request. How this class is doing the request without ajax? I can't understand wow this class works. Can anyone show me a better example?
There are two main options:
Put an iframe where you want the widget to go. Its src URL would be on the same server that will receive the AJAX call.
Use JSONP, which consists of inserting a script tag into the page to bypass the same-origin policy. This requires that the AJAX server wrap its JSON output in ?(...), where the URL includes callback=?. Then, as soon as a response has been received, start another request.