How to cache dynamic content in varnish? - varnish

I am working on an RoR app. I want to set a ttl for sold out items. The sold_out_status function returns the productIds of of sold-out products and is defined as:
def sold_out_status
query_str = params[:pids]
pids = query_str.split(",")
sold_outs = cache(request.fullpath.to_s, :expires_in => 3600.seconds.to_i) do
pids.select {|x| (Product.find(x).on_hand <= 0)}.join(",")
end
render :text => sold_outs
end
Is there a way I can cache the products in varnish and set a ttl for the sold-out products. Some research indicated that ESI could be a good idea. But, I could not find a detailed documentation which I could follow to implement this. Also, how can I access the output of sold_out_status in varnish?
EDIT: The sold out products on an page can be found by following a Request URL, the page corresponding to this URL contains ids of the sold-out products rendered as text.

Add the following in vcl_fetch:
sub vcl_fetch{
if (req.url ~ "^/taxons/sold_out_status.") {
std.syslog(180, "setting TTL for sold out products");
set beresp.do_esi = true;
set beresp.ttl = 1s;
return(deliver);
}

Related

How to filter large array based on "in-between" value in a sub-array? (Node.js)

I have a large database of items that have somewhat fluid statuses. I need to get an array of those items based on what each items's status was on a given date.
Here's an excerpt from an example record:
{"status":[
{"date":{"$date":"2019-06-14T06:17:41.625Z"},"statusCode":200},
{"date":{"$date":"2019-11-04T02:02:58.020Z"},"statusCode":404},
{"date":{"$date":"2020-08-07T01:11:16.184Z"},"statusCode":200},
{"date":{"$date":"2020-08-07T03:54:09.703Z"},"statusCode":404}
]}
Using this example, the status on 2020-01-13 would be 404 (as it would be also on 2020-01-12 or any other givenDate until the status changed back to 200).
So how would I filter my big array to this record (and others like it) to only items with status 404 as of 2020-01-13? (And I would do the same for 200.)
Note that I can't simply filter for objects with date < givenDate && statusCode == 200 because that would ignore if the status changed after those records. (The above example would return for either 200 or 404 since both records exist before givenDate.)
My only idea at the moment is that I could first filter the status array to anything before givenDate, and then compare based on the last record (since this filtered array's last record would then always be before givenDate). But this seems more complicated than necessary.
Processing time isn't important to me on this because I'm trying to make some one-time corrections to past statistics.
Thanks in advance!
A bit verbose, but I think this should do what you want.
var feedHistory = {"status":[
{"date":{"$date":"2019-06-14T06:17:41.625Z"},"statusCode":200},
{"date":{"$date":"2019-11-04T02:02:58.020Z"},"statusCode":404},
{"date":{"$date":"2020-08-07T01:11:16.184Z"},"statusCode":200},
{"date":{"$date":"2020-08-07T03:54:09.703Z"},"statusCode":404}
]};
const filterByStatus = (feedHistory,statusDate) => {
let foundRecord = false;
feedHistory.forEach((record) => {
let recordDate = new Date(Date.parse(record.date['$date']));
if (recordDate < statusDate && (!foundRecord || foundRecord.parsedDate < recordDate)) {
record.parsedDate = recordDate;
foundRecord = record;
}
});
return foundRecord;
};
var statusDate = new Date('2019-06-15');
var statusOnDate = filterByStatus(feedHistory.status,statusDate);
console.log(`On ${statusDate} the status was ${statusOnDate.statusCode}`);

Can I create a custom list of values like ACL in Varnish 4?

I'm using Varnish version 4. I'd like to know if VCL allows a custom and reusable list of values like ACL. I want to use it to check against visitors' cookies. If he is a moderator, don't serve cached content.
Cookie String:
session=9urt2jipvkq77brfrf; UserID=158
Code:
acl moderator{
"158";
"114";
}
sub vcl_recv {
set req.http.UserID = regsub(req.http.Cookie,".*UserID=(\d+).*","\1"); // 158
if(req.http.UserID ~ moderator){ // 158 found in the moderator list
return(pass);
}
}
Short answer: no
ACL (access control list) is only used for specifying different IPs/hosts.
However you can use a VMOD to accomplish this. Checkout Variable
It has some basic functions for setting and getting variables.
set("my_var", "this is the value")
set req.http.x-my-var = get("my_var")
There is also some more advanced functions, like setting multiple variables from a single string using regex.
variable.regset("ttl:d=\1s,grace:d=\2s", "^(?:.*,)?max-age=([0-9]+)(?:+([0-9]+))", beresp.http.Surrogate-Control);
set beresp.ttl = variable.get_duration("ttl");
set beresp.grace = variable.get_duration("grace");
ttl is the name of the variable, grace is the name of the second variable
\1 & \2 are simple back-references to the regex
:d specifies the type, in this case duration
Your list of user id:s
You could just set them in a comma separated string
set("moderators", ",158,114,") //Notice the starting and ending comma-sign
if(","+req.http.UserID+"," ~ get("moderators")){
return(pass);
}

"Field Name was not found in the send source"

I'm a bit new to ExactTarget in general so I apologize if this has already been answered (if it has, I can't find it anywhere).
I am attempting to create an email which will conditionally display n of 50 bulleted lists containing links to product information. However, whenever I attempt to send this email, I receive the following error message:
Other errors found in the email.
Category: AMP Script
Functions and Custom Objects:('
Field Name %%F50%% was not found in the send source.
Category: AMP Script
The second paragraph of the error message is repeated 50 times total (one for each field).
I cannot seem to figure-out why this issue is occurring:
I have a Data Extension with data for each field mapped in it.
I have imported valid data from a CSV to the data extension.
I have a list of valid subscribers to whom I am attempting to distribute.
I have an email template with custom areas inside each of which check if the subscriber has a "true" value for each field and shows/hides the content snippets on that basis.
I have 50x content snippets (one for each field).
There has to be something I'm missing here. Any ideas?
Thanks!
If the 50 fields are not in your sending data extension, you'll need to retrieve them with a script something like this. Note the values are displayed with%%=v(#DEColumn1)=%%:
%%[
var #rows, #row, #rowCount, #numRowsToReturn, #lookupValue, #i
set #lookupValue = "whee"
set #numRowsToReturn = 0 /* 0 means all */
set #rows = LookupOrderedRows("DataExtensionName",#numRowsToReturn,"DEColumn1 desc, DEColumn2 asc","LookupColumn", #lookupValue)
set #rowCount = rowcount(#rows)
if #rowCount > 0 then
for #i = 1 to #rowCount do
var #DEColumn1, #DEColumn2
set #row = row(#rows,#i) /*get row based on loop counter */
set #DEColumn1 = field(#row,"DEColumn1")
set #DEColumn2 = field(#row,"DEColumn2")
]%%
Row %%=v(#i)=%%, DEColumn1 is %%=v(#DEColumn1)=%%, DEColumn2 is %%=v(#DEColumn2)=%%
%%[ next #i ]%%
%%[ else ]%%
No rows found
%%[ endif ]%%
Also, there are a lot more people answering SFMC questions over at salesforce.stackexchange.com -- mostly tagged with marketing-cloud, ampscript.

Specify a page for pagination - Laravel 4

I'm trying to "remember" the page the user was on as he browses through records so that when he returns to the list, he is returned to the page where he left off.
How do I change the "current page" value for paginator?
I've tried Input::set('page', $x); but there's no such function.
$_GET['page'] = $x; doesn't work too.
This the code:
$list = Publication::orderBy($this->data['sort_by'], $this->data['order_by']);
foreach ($this->data['filter_data'] AS $field => $value) {
$list->where($field, 'LIKE', "%$value%");
}
$this->data['list'] = $list->paginate(15);
I looked at the API --- turns out this is much easier now in 2014.
All you have to do is set
Paginator::setCurrentPage(2);
any time before you call ->paginate(), I believe, and it should override the page set (or not set) by ?page=.
You can adjust the page of the pagination environment through the DB connection.
DB::getPaginator()->setCurrentPage(2);
Not entirely sure but you might be able to go through your model with something like this.
Publication::getConnection()->setCurrentPage(2);
If the above isn't working (as it seems from your comment), then do it with an instance of Publication.
$publication = new Publication;
$publication->getConnection()->setCurrentPage(2);
$list = $publication->orderBy(...);
Try
$pager->setCurrentPage(2);

How do I invalidate an object in the Varnish cache when it has zero length?

I am trying to work around a backend server that will from time to time start serving up blank pages with a 200 OK response, by having Varnish continue to serve old cached versions of these pages (aka. grace mode).
First I tried inspecting the response in vcl_fetch, but as far as I have been able to figure out, there is no way to figure out the content length in vcl_fetch. Then I attempted to do the work in vcl_deliver (where the Content-Length header is available). That does work, but I can't figure out how to expunge the bad cached object (the one with the blank page), so that seems to be a no-go.
I was advised to set obj.grace and obj.ttl in vcl_deliver, and this is my current code:
sub vcl_deliver {
# If the front page is blank, invalidate this cached object, in hope
# that we'll get a new one.
if (req.url == "/" && std.integer(resp.http.content-length, 0) < 1000) {
set obj.grace = 0m;
set obj.ttl = 0m;
return(restart);
}
}
However, Varnish doesn't like that, and gives me this error when I try to load the VCL:
Message from VCC-compiler:
'obj.grace': cannot be set in method 'vcl_deliver'.
At: ('input' Line 146 Pos 9)
set obj.grace = 0m;
--------#########------
I get the same error for obj.ttl if I remove the obj.grace line – neither seems to be writable in vcl_deliver, even though the docs say otherwise. This is on Varnish 3.0.2.
What i did is check the Content-Length for 0 and 20 in sub_vcl_fech and restart when this happens
if (beresp.http.Content-Length == "0" || beresp.http.Content-Length == "20"){
return(restart);
}
content length of 20 is what my server returned when an error occurs.
in the sub vcl_recv i added a check on amount of restarts maximum of 2
if(req.restarts == 2){
error 500 req.http.host;
}
Option 2
another option i got from the varnish documentation.
https://www.varnish-cache.org/docs/3.0/tutorial/handling_misbehaving_servers.html
Throw error in the vcl_fetch like: error 751 req.http.host;
add magic marker in the vcl_error.
add return(restart); in the vcl_error
check for magic marker set in vcl_recv or vcl_fetch
Doing this in vcl_deliver is too late. This sub is called right before sending content to the client and obj should not be available anymore (only 'resp' which doesn't contain any ttl or grace parameter).
Did you try doing this in vcl_fetch ? You won't need to call 'restart' but directly 'hit_for_pass'.
Anyway, (not sure about that) I don't think grace mode can be used depending on response content since it's supposed to be triggered when you can't get any content update (backend failure). Maybe it could work by changing backend to a "zombie" one and restarting request but for sure, once you're in vcl_fetch, a response is...fetched, and grace mode won't trigger.

Resources