A LineRate script with lengthy initialization process
In some cases, your LineRate script needs to gather necessary information before processing any incoming HTTP request. It could be user profiles such as customer categories (e.g., paid vs. free, or test vs. live), GeoIP database, list of sites that should be blocked, or data translation tables for converting contents of specific headers. For this, typically, the script needs to access external database or file servers, which may take some time.
For this type of synchronous (or sequential) processing, we normally employ the Async NPM module: e.g.,
async.series([initialization, process]);
The async.series() method ensures that the
process
function (2nd element in the array) kicks off after completion of the initilization
function (1st element).
Well, what happens when HTTP requests hit the LineRate while the script is still initializing? The LineRate will pass the requests through to backend servers without any processing. This means, under A/B Web traffic steering scenario where test and live customers are directed to the test and production servers respectively for example, both customers are forwarded to the same server (where the requests go to depends on how you configure LineRate). This might be acceptable behavior in some implementations, but in some cases, you might want to block access completely until the system becomes ready with full information.
This can be achieved by preparing a function that starts up quick and runs only during the initialization process. Using the async.series() method, it can be coded as below:
async.series([process_pre, initialization, process_post]);
The
process_pre
registers a listener function to the request
event. The listener is active once process_pre
kicks in and during the process of initialization. Once the initialization completes, you de-register the existing listener for the request
event before you register the new listener for the post-initialization processing. De-registration is necessary because you can only register one listener to virtualServer's request event. This can be done by the Event.removeListener() method as below.
VirtualServerModule.removeListener('request', process_pre);
In the sample Node.js code below, the initialization function (initialize) does nothing but 10s sleep. While initializing, the 1st listener (
process_pre) returns the 503 "Service Unavailable" error message to clients. It also returns the Retry-After header to indicate the maximum time for the service to become fully available. You can replace it with your intialization codes (e.g., database population or remote file access).
As this is just an sample, the post-initialization function (
process_post
) does not do much: It just inserts a proprietary header for debugging purpose.
'use strict'; var vsm = require('lrs/virtualServerModule'); var async = require('async'); var vso; var sleep = 10; // sleep time (s) var message = 'Service Unavailable\nTemporarily down for maintenance' // Script initialization required to do any request processing // - assuming it takes a long time to complete. var initialize = function(callback) { setTimeout(function() { callback(null, 'initialization completed'); }, sleep*1000); }; // Request handling before initialization // Send back 503 "Service Unavailable" var process_pre = function(servReq, servResp, cliReq) { servResp.writeHead(503, { 'Content-Type': 'text/plain', 'Content-Length': message.length, 'Retry-After': sleep}); servResp.end(message); }; var register_pre= function(callback) { vso.on('request', process_pre); callback(null, 'pre-initialization processing'); }; // request handling (after initialiation) var process_post = function(servReq, servResp, cliReq) { // do whatever you need to do to the requests with the info obtained // through the initialization process servReq.addHeader('X-LR', 'LineRate is ready.'); cliReq(); }; var register_post = function(callback) { vso.removeListener('request', process_pre); vso.on('request', process_post); callback(null, 'post-initialization processing'); }; vsm.on('exist', 'vs40', function(v) { vso = v; async.series([register_pre, initialize, register_post], function(e, stat) { if (e) console.error(e); else console.log(stat); }); }); if (! vso) { console.log('vs40 not found. Script is not running properly.'); }
LineRate may spawn multiple HTTP processing engines (called lb_http) depending on a number of vCPUs (cores/hyper-threads). In the code above, all the engines perform the same initialization process. However, if the result from an initialization can be shared amongst the engines, you might want to run it just once on a designated process for all the processes: e.g., data population onto a shared database. While you can achieve this by running the initialization only on a master process using Process.isMaster() method (LineRate extension), you now need to come up with a mechanism to share information amongst the processes. You can find data sharing methods in "A Variable Is Unset That I Know Set" in our Product Documentation or "LineRate and Redis pub/sub" DevCentral article.
Please leave a comment or reach out to us with any questions or suggestions and if you're not a LineRate user yet, remember you can try it out for free.