-
Notifications
You must be signed in to change notification settings - Fork 0
Tutorial
- Check if user ip is at specific range.
- City Lookup based answer
- Choice based on CDN RUM uptime
- Different answers for different countries.
- Countries based answers with random selection.
First of all, couple of words regarding script structure. All main Custom Answer logic is placed inside asynchronous function onRequest
. It has two params: req
(Request) and res
(Response).
- req (Request) - provides you with all available information regarding requesting user.
-
res (Response) - helps you to form 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 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 user IP at our IRequest interface:
...
readonly ip: TIp;
...
So log in, proceed to FlexBalancers page, add new FlexBalancer with Custom answer, set fallback (we just made it as fallback.mydomain.com
) and you will be redirected to 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 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 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:
async 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 res;
}
}
And if IP is not at that range it should return answer.otherranges.net
with TTL 15:
...
}
res.setAddr('answer.otherranges.net');
res.setTTL(15);
return res;
}
So finally, our answer looks like:
const ipFrom = '134.249.200.0';
const ipTo = '134.249.250.0';
async 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 res;
}
// IP is not in that range
res.setAddr('answer.otherranges.net');
res.setTTL(15);
return res;
}
Now press Test and Publish
Button. This is important otherwise nothing will work.
And now when we dig my balancer with 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 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 user IP. For example, you want to assign specific answer for all users at 100km radius from Amsterdam
. From MaxMind GeoLite2 Databases we get 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 ware going to use
You can find out more information in our documentation Custom Answers API
First let's define city and 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 Promise. If resolved - it gives us bool
result.
Now we implement the simple logic:
const userInRadius = await lookupCity(req.ip, cityToCheckGeoNameId, distanceThreshold); // 100km from Amsterdam?
if(userInRadius === true) { // if 'yes'
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
async function onRequest(req: IRequest, res: IResponse) {
const userInRadius = await lookupCity(req.ip, cityToCheckGeoNameId, distanceThreshold); // 100km from Amsterdam?
if(userInRadius === true) { // if 'yes'
res.setAddr(cityToCheckAnswer); // set answer for Amsterdam
return;
}
res.setAddr(defaultAnswer); // It is not Amsterdam, return answer for other cities
return;
}
Let's imagine that you have two answers hosted on two different CDN providers: jsdelivr.myanswer.net
and googlecloud.myanswer.net
.
CDNPerf provides CDN Performance value, based on RUM (Real User Metrics) data from users all over the world. You want to check that Performances and return answer from CDN with better performance. And if performances are equal - return random answer.
First, let make an array of our answers:
const answers = [
'jsdelivr.myanswer.net',
'googlecloud.myanswer.net'
];
Then, get CDN Performance values, using fetchCdnRumPerformance
function, provided by our Custom Answers API:
// get Performance values
const jsDelivrPerf = fetchCdnRumPerformance('jsdelivr-cdn');
const googleCloudPerf = fetchCdnRumPerformance('google-cloud-cdn');
Now, if values are equal - we'll return random answer from our array:
// if Performance values are equal - return random answer
if(jsDelivrPerf == googleCloudPerf) {
const randomAnswer = answers[Math.floor(Math.random()*answers.length)];
response.setAddr(randomAnswer);
return response;
}
And if those are not equal - return answer from CDN with higher performance:
// get answer based on higher performance
const answer = (jsDelivrPerf > googleCloudPerf) ? answers[0] : answers[1];
response.setAddr(answer); // return answer
return response
As the result, we get our final script:
const answers = [
'jsdelivr.myanswer.net',
'googlecloud.myanswer.net'
];
async function onRequest(request: IRequest, response: IResponse) {
// get Performance values
const jsDelivrPerf = fetchCdnRumPerformance('jsdelivr-cdn');
const googleCloudPerf = fetchCdnRumPerformance('google-cloud-cdn');
// if Performance values are equal - return random answer
if(jsDelivrPerf == googleCloudPerf) {
const randomAnswer = answers[Math.floor(Math.random()*answers.length)];
response.setAddr(randomAnswer);
return response;
}
// get answer based on higher performance
const answer = (jsDelivrPerf > googleCloudPerf) ? answers[0] : answers[1];
response.setAddr(answer); // return answer
return response;
}
And that's it!
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 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 default response first, it will be used if user country is not in that countries list created above:
async 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
, and it will return default response:
async 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 res;
}
...
}
Then, let's cycle through our country objects and if user country matches any of our listed countries - set appropriate answer.
async 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 res; // 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 res; // 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 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 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, 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 user country:
async 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 user country matches any of those listed in our configuration:
async 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 res;
}
}
...
}
And if we have user with country not listed at our configuration - we should return default answer:
async function onRequest(req: IRequest, res: IResponse) {
...
res.setAddr('answer.othercountries.net');
res.setTTL(defaultTtl);
return res;
}
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)];
};
async 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;
}
}
res.setAddr('answer.othercountries.net');
res.setTTL(defaultTtl);
return res;
}
So, now if we dig our balancer with 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.