LineRate: moveRequest() example

The moveRequest() feature of LineRate is unique in that it allows the ability to direct HTTP requests between virtual-servers based on use case requirements.

A possible use case may be steering based on type of video file format requested, the requests may be diverted to appropriate real-servers via virtual-servers that can serve the specific file.

Another possible use case may be steering based on a 'trial' or 'paid' user. The example script below expands on this use case.  The example script populates the on-system redis database with 'fictitious' users and their status as a subscriber of the services rendered by the site. This is to aid in demonstrating the script.

The site operates two pools real-servers  - one for paid customers and another for trial users.  The reasons for operating separate pool may be operational for example redundancy/backup/resiliency requirements for paid user.  The possibilities are innumerable. 

Each pool is attached to a virtual-server – a paid and a trial virtual-server.  One virtual server is attached to an IP interface for request and responses.  Requests received by this virtual server is parsed for user name in the query string.  The given user is checked against the redis server as a known user.  In the absence of the user's name, a new user is added as a trial customer.  Here is a diagram to aid this description.

Here is some module and variable instantiation to get things started:

// Move request example
"use strict";

var vsm = require('lrs/virtualServerModule');
var redis = require('redis');
var url = require('url');
var async = require('async');
var trialUsersServer = undefined;
var client = redis.createClient();
var userAccountDB = 1;

Redis

Redis is an advanced key-value cache and store.  As we intend to store usernames (as key) and a status (as value) it fits our requirement aptly.  The below code connects to the redis server and populate with sample entries for demonstration only.  Due the asynchronous nature of the Node.js, while redis is being populated the script is written such that incoming requests are handled by the paid virtual-server by default.  This can be customized such that an appropriate page can be presented when the user is not a paid user.

client.select(userAccountDB, function(err, result){
  if(err){
    return err;
  }
});

client.on('error', function(err){
  console.log('Error ' + err);
});

// Connect to the redis server and populate with sample entries
client.on('connect', function(){
  console.log("Connected to redis server");
  client.mset("John", "trial", "Jane", "paid", "Donald", "trial");
});

Async waterfall

When an HTTP request is received by LineRate serial processing is needed to determine which virtual server to send the request to. The async module’s waterfall method fits this need.  This will invoke getUsername(), getUserStatus(), and processRequest() serially with the necessary information passed to the next function in the series.

var onUserRequest = function(servReq, servResp, cliReq){
  async.waterfall([
      function(callback){
        getUsername(servReq, callback);
      },
      function(username, callback){
        getUserStatus(username, callback);
      },
      function(isTrialUser, callback){
        processRequest(isTrialUser, servReq, servResp, cliReq);
      }
    ], function(err, result){
      if(err){
        console.log("Error processing request " + err);
        processRequest(false, servReq, servResp, cliReq);
      }
    });
}

Here is the method to extract the username from query string.

function getUsername(servReq, callback){
  var url_parts = url.parse(servReq.url, true);
  var query = url_parts.query;
  return callback(null, query.name);
}

Here is the method to determine whether the user is a trial or paid user of the services.  If the username i.e. the ‘name’ query string is absent in the request then we don’t divert to the trial server. One can tweak this to their requirement. I am forwarding it to the paid-server for simplicity of the example. If the username is not found in the redis database, the user is added as a trial user.  More customization can be done here such as expiring the entry after a certain time.  For the sake of simplicity the user is added until removed.

function getUserStatus(username, callback){
  if (!username){
    console.log("No username found");
    return callback(null, false);
  }

  // If the query string contains a name, process request
  // based on user's trial or paid 'status'
  client.get(username, function(err, result){
    if (err){
      throw err;
    }

    if (!result){
      console.log("User " + username + " not found");
      client.set(username, "trial", function(err, result){
        if(err){
          return callback(err);
        }

        if (result){
          console.log("User " + username + " added");
          return callback(null, true);
        }
      });
    }
    else{
      console.log("User " + username + " found");
      if (result === "trial"){
        return callback(null, true);
      }
      else if(result === "paid"){
        return callback(null, false);
      }
      else{
        console.log("Parse error from redis value");
        return callback(null, false);
      }
    }
  });
}

The below method forwards the request to the appropriate virtual-server based on the status of the user’s account i.e. paid or trial. The original request is received by 'paidUsersServer'. When the query string username is a trial user, it forwards it to 'trialUsersServer'.  In the case of a paid user, the request is processed by 'paidUsersServer' itself.

function processRequest(isTrialUser, servReq, servResp, cliReq){
  if (trialUsersServer && isTrialUser){
    trialUsersServer.moveRequest(servReq, servResp);
  }
  else{
    cliReq();
  }
}

Instantiate the paid server and trial server objects based the appropriate virtual-servers.

var onPaidUsersServerExists = function(vs){
  console.log("Paid users' server ready");
  vs.on('request', onUserRequest);
}

vsm.on('exist', 'paidUsersServer', onPaidUsersServerExists);

var onTrialUsersServerExists = function(vs){
  console.log("Trial users' server ready");

  trialUsersServer = vs;
}

vsm.on('exist', 'trialUsersServer', onTrialUsersServerExists);

Now for the tests. Server 1 and Server 2 are configured as real-servers attached to the paid virtual-server. Server 3 is configured as real-server attached to the trial virtual-server. Please refer to the LineRate config provided. All real-servers return information about the request.

ubuntu:~$ curl http://192.0.2.1/?name=John

Server 3 Page Remote address: 192.168.112.145
Remote port: 20404
Request method: GET
Request time: Tuesday, February 10, 2015 12:30 pm
Server address: 192.168.112.149
Server port: 80

ubuntu:~$ curl http://192.0.2.1/?name=Jane

Server 2 Page Remote address: 192.168.112.145
Remote port: 21003
Request method: GET
Request time: Tuesday, February 10, 2015 12:36 pm
Server address: 192.168.112.142
Server port: 80

You can clone it all here:

vsm-move-request.js

move-request.cfg – redacted

Updated Jun 06, 2023
Version 2.0

Was this article helpful?

No CommentsBe the first to comment