-
Notifications
You must be signed in to change notification settings - Fork 0
Tutorial
- Lesson 1: Check if the user ip is at specific range.
- Lesson 2: City Lookup based answer.
- Lesson 3: Choice based on the CDN RUM uptime.
- Lesson 3.1: CDN RUM performance based choice.
- Lesson 4: The answer based on Uptime Monitors.
- Lesson 5: Different answers for different countries.
- Lesson 6: Countries based answers with random selection.
First of all, couple of words regarding the script structure. All main Custom Answer logic is placed inside the 'Main' function onRequest
. It has two params: req
(Request) and res
(Response).
- req (Request) - provides you with all available information regarding user request.
-
res (Response) - helps you to form the specific answer, has
setAddr
andsetTTL
methods for that.
Our types, interfaces and functions are described here: Custom-Answers-API
First of all, let's create the simpliest answer. It will check the user ip and if it is in specific range - return answer.formyiprange.net
with TTL 10. If it is not - return answer.otherranges.net
with TTL 15.
We have the user IP at our IRequest interface:
...
readonly ip: TIp;
...
So log in, proceed to the FlexBalancers page, add new FlexBalancer with the Custom answer, set a fallback (we just made it as fallback.mydomain.com
) and you will be redirected to the Editing page. Flex creations and management is described at our 'Quick Start` Document - you may want to take a look at that: Quick Start
Let's set up some IP ranges for specific answer:
const ipFrom = '134.249.200.0';
const ipTo = '134.249.250.0';
Let's presume that our current ip is at that range, so it should be processed by custom answer. You can use your own IP with own range, just be sure that your IP is in that range.
So let's edit our 'onRequest' logic. We will use our predefined isIpInRange(ip: TIp, startIp: TIp, endIp: TIp):boolean function:
function onRequest(req: IRequest, res: IResponse) {
if (isIpInRange(req.ip, ipFrom, ipTo) === true) { // Check if IP is in range
res.setAddr('answer.formyiprange.net'); // Set 'addr' for answer
res.setTTL(10); // Set TTL
return;
}
}
And if the user IP is not at that range it should return answer.otherranges.net
with TTL 15:
...
}
res.setAddr('answer.otherranges.net');
res.setTTL(15);
return;
}
So finally, our answer looks like:
const ipFrom = '134.249.200.0';
const ipTo = '134.249.250.0';
function onRequest(req: IRequest, res: IResponse) {
if (isIpInRange(req.ip, ipFrom, ipTo) === true) { // Check if IP is in range
res.setAddr('answer.formyiprange.net'); // It is, set 'addr' for answer
res.setTTL(10); // Set TTL
return;
}
// IP is not in that range
res.setAddr('answer.otherranges.net');
res.setTTL(15);
return;
}
Now press the Test and Publish
Button. This is important otherwise nothing will work.
And now when we dig our balancer with the IP address inside that range, we get:
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 10 IN CNAME answer.formyiprange.net.
and if we are using another IP that is not in the predefined range we get
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 15 IN CNAME answer.otherranges.net.
Pretty simple, isn't it?
We provide useful set of lookup-type
functions - those can get user location information based on the user IP.
For example, you want to assign specific answer for all users at 100km radius from Amsterdam
. From MaxMind GeoLite2 Databases we get the Amsterdam
geoname ID and it is equal to 2759794
.
Our lookup
functions can be used with ip
as a single parameter and also accept additional parameters:
lookupCity(ip: string) // We won't need this now. And in most cases we have that info at req.location.city
...
lookupCity(ip: string, target: number, threshold: number) // This is one we need
You can find out more information in our documentation Custom Answers API
First let's define the city and the answers:
const cityToCheckGeoNameId = 2759794; // our city geoname ID
const cityToCheckAnswer = 'amsterdam.myanswer.net'; // answer for that city
const distanceThreshold = 100; // 100 km radius
const defaultAnswer = 'othercity.myanswer.net'; // answer for other cities
We will use lookupCity
function three arguments (user IP, city geoname ID and threshold), that returns bool
result.
Now we implement the simple logic:
const userInRadius = lookupCity(req.ip, cityToCheckGeoNameId, distanceThreshold);
if(userInRadius === true) { // 'yes', user is in 100km from Amsterdam
res.setAddr(cityToCheckAnswer); // set answer for Amsterdam
return;
}
res.setAddr(defaultAnswer); // It is not Amsterdam, return answer for other cities
return;
So we have script:
const cityToCheckGeoNameId = 2759794; // our city geoname ID
const cityToCheckAnswer = 'amsterdam.myanswer.net'; // answer for that city
const distanceThreshold = 100; // 100 km radius
const defaultAnswer = 'othercity.myanswer.net'; // answer for other cities
function onRequest(req: IRequest, res: IResponse) {
const userInRadius = lookupCity(req.ip, cityToCheckGeoNameId, distanceThreshold);
if(userInRadius === true) { // 'yes', user is in 100km from Amsterdam
res.setAddr(cityToCheckAnswer); // set answer for Amsterdam
return;
}
res.setAddr(defaultAnswer); // It is not Amsterdam, return answer for other cities
return;
}
And our tests show us expected answer:
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 10 IN CNAME amsterdam.myanswer.net.
Another lookup
function that we provide is lookupAsn
, that returns info regarding the Autonomous System Number of the IP provided:
declare interface IAsnResponse {
readonly autonomousSystemNumber: number;
readonly autonomousSystemOrganization: string;
}
The simple case - if the user IP has the ASN 20473
- the answer should be 20473answer.myanswers.net
with the TTL 20
and if it is not -should return the fallback (remember, we just made it as fallback.mydomain.com
with the TTL 10
).
Ok, our constants will be:
const asnToCheck = 20473;
const asnAnswer = '20473answer.myanswers.net';
const asnTTL = 20;
And the whole code will be really simple:
const asnToCheck = 20473;
const asnAnswer = '20473answer.myanswers.net';
const asnTTL = 20;
function onRequest(req: IRequest, res: IResponse) {
let asnInfo = lookupAsn(req.ip);
if(asnInfo && asnInfo.autonomousSystemNumber == asnToCheck) {
res.setAddr(asnAnswer);
res.setTTL(asnTTL);
}
return; // either asn related data or fallback
}
Let's check how our script works:
For IPs with the ASN Number equal to 20473
:
;; ANSWER SECTION:
testcustom1.0b62ec.flexbalancer.net. 20 IN CNAME 20473answer.myanswers.net.
For other IPs:
;; ANSWER SECTION:
testcustom1.0b62ec.flexbalancer.net. 10 IN CNAME fallback.mydomain.com.
Great, we've done it.
Let's imagine that you have two answers hosted on two different CDN providers: jsdelivr.myanswer.net
and googlecloud.myanswer.net
.
CDNPerf provides the CDN Uptime value, based on the RUM (Real User Metrics) data from users all over the world. You want to check that Uptimes and return answer from CDN with better uptime. And if uptimes are equal - return random answer.
First, let make an array of our answers:
const answers = [
'jsdelivr.myanswer.net',
'googlecloud.myanswer.net'
];
Then, get the CDN Uptime values, using fetchCdnRumUptime
function, provided by our Custom Answers API:
// get Uptime values
const jsDelivrUp = fetchCdnRumUptime('jsdelivr-cdn');
const googleCloudUp = fetchCdnRumUptime('google-cloud-cdn');
Now, if values are equal - we'll return random answer from our array:
// if Uptime values are equal - return random answer
if(jsDelivrUp == googleCloudUp) {
const randomAnswer = answers[Math.floor(Math.random()*answers.length)];
res.setAddr(randomAnswer);
return;
}
And if those are not equal - return answer from the CDN with better uptime:
// get answer based on higher uptime
const answer = (jsDelivrUp > googleCloudUp) ? answers[0] : answers[1];
res.setAddr(answer); // return answer
return;
As the result, we get our final script:
const answers = [
'jsdelivr.myanswer.net',
'googlecloud.myanswer.net'
];
function onRequest(req: IRequest, res: IResponse) {
// get Uptime values
const jsDelivrUp = fetchCdnRumUptime('jsdelivr-cdn');
const googleCloudUp = fetchCdnRumUptime('google-cloud-cdn');
// if Uptime values are equal - return random answer
if(jsDelivrUp == googleCloudUp) {
const randomAnswer = answers[Math.floor(Math.random()*answers.length)];
res.setAddr(randomAnswer);
return;
}
// get answer based on higher uptime
const answer = (jsDelivrUp > googleCloudUp) ? answers[0] : answers[1];
res.setAddr(answer); // return answer
return;
}
And that's it! So now we get either:
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 10 IN CNAME jsdelivr.myanswer.net.
or
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 10 IN CNAME googlecloud.myanswer.net.
depending on the best CDN uptime.
CDNPerf also provides the CDN Performance value, also based on the Real User Metrics data collected from users all over the world. So you can use that performance as the criteria.
The code is very similar to the previous one - let's just focus on differences. Custom Answers API provides fetchCdnRumPerformance
function, all we need is to modify the code of the previous lesson:
// get Performance values
const jsDelivrPerf = fetchCdnRumPerformance('jsdelivr-cdn');
const googleCloudPerf = fetchCdnRumPerformance('google-cloud-cdn');
If the performances are not equal - we return answer from the CDN with faster performance. This is quite opposite to Uptime - the bigger Uptime is - the better and the lower Performance value is (query speed in milliseconds) - the faster query speed is:
// get answer based on faster performance
const answer = (jsDelivrPerf < googleCloudUp) ? answers[0] : answers[1];
res.setAddr(answer); // return answer
return;
As the result, we get our script:
const answers = [
'jsdelivr.myanswer.net',
'googlecloud.myanswer.net'
];
function onRequest(req: IRequest, res: IResponse) {
// get Performance values
const jsDelivrPerf = fetchCdnRumPerformance('jsdelivr-cdn');
const googleCloudPerf = fetchCdnRumPerformance('google-cloud-cdn');
// if query speeds are equal - return random answer
if(jsDelivrPerf == googleCloudPerf) {
const randomAnswer = answers[Math.floor(Math.random()*answers.length)];
res.setAddr(randomAnswer);
return;
}
// get answer based on faster performance
const answer = (jsDelivrPerf < googleCloudUp) ? answers[0] : answers[1];
res.setAddr(answer); // return answer
return;
}
So we get either:
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 10 IN CNAME jsdelivr.myanswer.net.
or
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 10 IN CNAME googlecloud.myanswer.net.
depending on the faster CDN performance.
There is another way to perform balancing based on Uptime
, let's take Monitors-based
example.
PerfOps provides Monitor Uptime feature that allows you to set monitor to each of your answers and use that uptime statistics for balancing. Let's use that statistics in our example.
Each monitor has its own ID, that is listed at 'Monitors page'. In our example case that IDs are 593
for the first.myanswer.net
and 594
for the second.myanswer.net
.
First, let's describe our answers and related monitors:
const answerOne = {
answer: 'first.myanswer.net',
monitor: 593 as TMonitor
}; // 'first' answer and its monitor
const answerTwo = {
answer: 'second.myanswer.net',
monitor: 594 as TMonitor
}; // 'second' answer and its monitor
Notice, that the Monitor IDs must be of TMonitor
type:
declare type TMonitor = 593 | 594; // your monitor IDs
And we are going to use our fetchMonitorUptime(monitor: TMonitor) and isMonitorOnline(monitor: TMonitor) functions, described at Custom Answers API.
Now, let's write our script. First, we check if our monitors are online:
// check if Monitors are online
const firstOnline = isMonitorOnline(answerOne.monitor);
const secondOnline = isMonitorOnline(answerTwo.monitor);
And fetch uptime results or set uptime to 0 depending on online status:
// get Monitor Uptime values if Monitors are online, otherwise set it to 0
const firstUp = (firstOnline === true) ? fetchMonitorUptime(answerOne.monitor) : 0;
const secondUp = (secondOnline === true) ? fetchMonitorUptime(answerTwo.monitor) : 0;
The rest of the code will be quite similar to our previous example so we won't explain it in details. Finally, we get:
const answerOne = {
answer: 'first.myanswer.net',
monitor: 593 as TMonitor
}; // 'first' answer and its monitor
const answerTwo = {
answer: 'second.myanswer.net',
monitor: 594 as TMonitor
}; // 'second' answer and its monitor
function onRequest(req: IRequest, res: IResponse) {
// check if Monitors are online
const firstOnline = isMonitorOnline(answerOne.monitor);
const secondOnline = isMonitorOnline(answerTwo.monitor);
// get Monitor Uptime values if Monitors are online, otherwise set it to 0
const firstUp = (firstOnline === true) ? fetchMonitorUptime(answerOne.monitor) : 0;
const secondUp = (secondOnline === true) ? fetchMonitorUptime(answerTwo.monitor) : 0;
// if Uptime values are equal - return random answer
if(firstUp == secondUp) {
const answers = [answerOne.answer, answerTwo.answer]; // form answers array
const randomAnswer = answers[Math.floor(Math.random()*answers.length)];
res.setAddr(randomAnswer);
return;
}
// get answer based on higher uptime
const answer = (firstUp > secondUp) ? answerOne.answer : answerTwo.answer;
res.setAddr(answer); // return answer
return;
}
Here we go!
And we get either:
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 10 IN CNAME first.myanswer.net.
or
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 10 IN CNAME second.myanswer.net.
depending on our Monitor Uptimes.
Let's take a look at a little bit more complicated case.
Imagine that you have three different 'addresses' for the US, France and Ukraine. Those are 'us.myanswers.net', 'fr.myanswers.net' and 'ua.myanswers.net'. And you want to use country-based answer depending on location user came from.
Our Request
already can handle user locations:
readonly location: {
...
country?: TCountry;
...
};
And TCountry is the list of countries ISO-codes (can be found at ISO codes on Wikipedia).
declare type TCountry = 'DZ' | 'AO' | 'BJ' | 'BW' | 'BF' ... 'PR' | 'GU';
First of all, let's create the array of country objects
const countries = [
{
iso: 'FR', // country ISO code
answer: 'fr.myanswers.net', // answer 'addr'
ttl: 10 // answer 'ttl'
},
{
iso: 'UA',
answer: 'ua.myanswers.net',
ttl: 11
},
{
iso: 'US',
answer: 'us.myanswers.net',
ttl: 12
}
];
Let's set the default response first, it will be used if the user country is not in that countries list created above:
function onRequest(req: IRequest, res: IResponse) {
res.setAddr('answer.othercountries.net');
res.setTTL(15);
...
}
So let's check & process the case when country
is empty, does not have any value at req
, so it will return default response:
function onRequest(req: IRequest, res: IResponse) {
res.setAddr('answer.othercountries.net');
res.setTTL(15);
if (!req.location.country) { // unable to determine user country or it is empty
return;
}
...
}
Then, let's cycle through our country objects and if the user country matches any of our listed countries - set the appropriate answer.
function onRequest(req: IRequest, res: IResponse) {
res.setAddr('answer.othercountries.net'); // Set default addr
res.setTTL(15); // And default TTL
if (!req.location.country) { // If no country at request
return; // Use default answer
}
for (let country of countries) {
if(req.location.country == country.iso) { // If user country matches one of ours
res.setAddr(country.answer); // Set addr and ttl to response
res.setTTL(country.ttl);
}
}
return; // Return new res, or default if no country matches
}
That's it!
So, now, when we dig our balancer with IP from France - we get:
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 10 IN CNAME fr.myanswers.net.
with the US IP:
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 12 IN CNAME us.myanswers.net.
with the IP from Ukraine:
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 11 IN CNAME ua.myanswers.net.
And if we use, for example, Australian IP (that is not in the list) we get the default answer:
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 15 IN CNAME answer.othercountries.net.
Works great!
Now, let's create more complicated answer. This is modified and simplified (with Monitors removed) version of one of our sample scripts (also available at our repository).
The goal is to have two possible answers (candidates) for each country from our list and randomly select one of them if user country matches with any country from our list (we use the same countries: France, the US and Ukraine). And if no matches - return default answer.othercountries.net
addr.
Let's create configuration for countries:
const configuration = {
providers: [
{
name: 'us1', // candidate name
cname: 'usone.myanswers.com', // cname to pick for 'addr'
},
{
name: 'us2',
cname: 'ustwo.myanswers.com',
},
{
name: 'fr1',
cname: 'frone.myanswers.com',
},
{
name: 'fr2',
cname: 'frtwo.myanswers.com'
},
{
name: 'ua1',
cname: 'uaone.myanswers.com'
},
{
name: 'ua2',
cname: 'uatwo.myanswers.com'
}
],
countriesAnswersSets: { // lists of candidates-answers per country
'FR': ['fr1', 'fr2'],
'US': ['us1', 'us2'],
'UA': ['ua1', 'ua2']
},
defaultTtl: 20, // we'll use the same TTL everywhere
};
So, the answer, for example, for France, should be randomly picked one from frone.myanswers.com
and frtwo.myanswers.com
.
Let's define function for random selection:
/**
* Pick random item from array of items
*/
const getRandomElement = <T>(items: T[]): T => {
return items[Math.floor(Math.random() * items.length)];
};
Now it is onRequest time! First of all let's parse our configuration and determine the user country:
function onRequest(req: IRequest, res: IResponse) {
const {countriesAnswersSets, providers, defaultTtl} = configuration; // Parse config
let requestCountry = req.location.country as TCountry; // Get user country
...
}
Now, let's find if the user country matches any of those listed in our configuration:
function onRequest(req: IRequest, res: IResponse) {
...
// Check if user country was detected and we have it in list
if (requestCountry && countriesAnswersSets[requestCountry]) {
// Pick our candidate addrs and check if those also are proper candidates
let geoFilteredCandidates = providers.filter(
(provider) => countriesAnswersSets[requestCountry].includes(provider.name)
);
// If we get proper candidates list for particullar country- let's select one of them randomly
if (geoFilteredCandidates.length) {
res.setAddr(getRandomElement(geoFilteredCandidates).cname);
res.setTTL(defaultTtl);
return;
}
}
...
}
And if we have the user with a country not listed at our configuration - we should return the default answer:
function onRequest(req: IRequest, res: IResponse) {
...
res.setAddr('answer.othercountries.net');
res.setTTL(defaultTtl);
return;
}
We are done, here is our script :
const configuration = {
providers: [
{
name: 'us1', // candidate name
cname: 'usone.myanswers.com', // cname to pick for 'addr'
},
{
name: 'us2',
cname: 'ustwo.myanswers.com',
},
{
name: 'fr1',
cname: 'frone.myanswers.com',
},
{
name: 'fr2',
cname: 'frtwo.myanswers.com'
},
{
name: 'ua1',
cname: 'uaone.myanswers.com'
},
{
name: 'ua2',
cname: 'uatwo.myanswers.com'
}
],
countriesAnswersSets: { // lists of candidates-answers per country
'FR': ['fr1', 'fr2'],
'US': ['us1', 'us2'],
'UA': ['ua1', 'ua2']
},
defaultTtl: 20, // we'll use the same TTL
};
/**
* Pick random item from array of items
*/
const getRandomElement = <T>(items: T[]): T => {
return items[Math.floor(Math.random() * items.length)];
};
function onRequest(req: IRequest, res: IResponse) {
const {countriesAnswersSets, providers, defaultTtl} = configuration; // Parse config
let requestCountry = req.location.country as TCountry; // Get user country
// Check if user country was detected and we have it at our list
if (requestCountry && countriesAnswersSets[requestCountry]) {
// Pick our candidate addrs and check that those are proper candidates
let geoFilteredCandidates = providers.filter(
(provider) => countriesAnswersSets[requestCountry].includes(provider.name)
);
// If we get proper candidates list for particular country- let's select one of them randomly
if (geoFilteredCandidates.length) {
res.setAddr(getRandomElement(geoFilteredCandidates).cname);
res.setTTL(defaultTtl);
return;
}
}
res.setAddr('answer.othercountries.net');
res.setTTL(defaultTtl);
return;
}
So, now if we dig our balancer with the French IP we randomly get either:
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 20 IN CNAME frtwo.myanswers.com.
or
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 20 IN CNAME frone.myanswers.com.
For the US:
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 20 IN CNAME usone.myanswers.com.
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 20 IN CNAME ustwo.myanswers.com.
And for Ukraine those are:
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 20 IN CNAME uatwo.myanswers.com.
;; ANSWER SECTION:
testcustom.0b62ec.flexbalancer.net. 20 IN CNAME uaone.myanswers.com.
Congratulations! Everything works fine!
As we have mentioned - the last script was simplified version of one of our sample scripts, that are available at our repository. Feel free to investigate!
Create your first FlexBalancer
- Case 1.1: Provider Availability with Weights.
- Case 1.2: Availability based on Monitor Uptime.
- Case 2.1: Balancing based on the CDN with the better Performance.
- Case 3.1: Geolocation with excluded country.
- Case 3.2: The specific answer for the specific region.
- Case 3.3: The provider with the best performance for visitor's country.
- Case 4.1: Using country-based answers from remote sources.