Skip to content

Instantly share code, notes, and snippets.

@kaizhu256
Created April 25, 2018 12:42
Show Gist options
  • Save kaizhu256/bcc36587ddb7521ebc83a2982081acbc to your computer and use it in GitHub Desktop.
Save kaizhu256/bcc36587ddb7521ebc83a2982081acbc to your computer and use it in GitHub Desktop.
this zero-dependency example will demo a simple (60 sloc), high-performance express-middleware that can broadcast a single fs.readFile() operation to multiple server-requests
/*
* example.js
*
* this zero-dependency example will demo a simple (60 sloc), high-performance express-middleware
* that can broadcast a single fs.readFile() operation to multiple server-requests
*
*
*
* example usage:
* $ node example.js
*
* example output:
* [server] - listening on port 1337
* [client] - hammering file-server for 1000 ms with client-request "http://localhost:1337/example.js"
* [server] - broadcast 10083 bytes of data from task fs.readFile("example.js") to 352 server-requests in 229 ms
* [server] - broadcast 10083 bytes of data from task fs.readFile("example.js") to 459 server-requests in 296 ms
* [server] - broadcast 10083 bytes of data from task fs.readFile("example.js") to 642 server-requests in 299 ms
* [server] - broadcast 10083 bytes of data from task fs.readFile("example.js") to 353 server-requests in 166 ms
* [server] - broadcast 10083 bytes of data from task fs.readFile("example.js") to 1 server-requests in 1 ms
* [server] - handled 1807 server-file-requests total
* [client] - made 2100 client-requests total in 1022 ms
* [client] - (1807 client-requests passed)
* [client] - (293 client-requests failed, e.g. "connect ECONNRESET 127.0.0.1:1337")
*
* finished running stress-test
* feel free to continue playing with this file-server
* (e.g. $ curl http://localhost:1337/example.js)
* or press "ctrl-c" to exit
*/
/*jslint
bitwise: true,
browser: true,
maxerr: 4,
maxlen: 100,
node: true,
nomen: true,
regexp: true,
stupid: true
*/
(function () {
'use strict';
var local;
local = {};
local.middlewareFileServer = function (request, response, nextMiddleware) {
/*
* this express-middleware will serve files in an optimized manner by
* piggybacking multiple requests for the same file onto a single readFileTask
* 1. if readFileTask with the given filename exist, then skip to step 4.
* 2. if requested filename does not exist, then goto nextMiddleware
* 3. if readFileTask with the given filename does not exist, then create it
* 4. piggyback server-request onto readFileTask with the given filename
* 5. broadcast data from readFileTask to all piggybacked server-requests
* 6. cleanup readFileTask
*/
var fileExists, filename, modeNext, onNext, timeStart;
onNext = function (error, data) {
modeNext += 1;
switch (modeNext) {
case 1:
// init timeStart
timeStart = Date.now();
// init readFileTaskDict
local.readFileTaskDict = local.readFileTaskDict || {};
// init serverRequestsTotal
local.serverRequestsTotal = local.serverRequestsTotal || 0;
// get filename from request
filename = require('url').parse(request.url).pathname;
// security - prevent access to parent directory, e.g. ../users/john/.ssh/id_rsa
filename = require('path').resolve('/', filename).slice(1);
// init fileExists
fileExists = true;
// 1. if readFileTask with the given filename exist, then skip to step 4.
if (local.readFileTaskDict[filename]) {
modeNext += 2;
onNext();
return;
}
local.readFileTaskDict[filename] = [];
require('fs').exists(filename, onNext);
break;
case 2:
fileExists = error;
// 2. if requested filename does not exist, then goto nextMiddleware
if (!fileExists) {
modeNext = Infinity;
}
onNext();
break;
case 3:
// 3. if readFileTask with the given filename does not exist, then create it
require('fs').readFile(filename, function (error, data) {
modeNext = Infinity;
onNext(error, data);
});
onNext();
break;
case 4:
// 4. piggyback server-request onto readFileTask with the given filename
local.readFileTaskDict[filename].push(response);
break;
default:
// 5. broadcast data from readFileTask to all piggybacked server-requests
local.readFileTaskDict[filename].forEach(function (response) {
local.serverRequestsTotal += 1;
if (error) {
console.error(error);
response.statusCode = 500;
response.end('500 Internal Server Error');
return;
}
response.end(data);
});
console.error('[server] - broadcast ' + (data || '').length +
' bytes of data from task fs.readFile(' + JSON.stringify(filename) + ') to ' +
local.readFileTaskDict[filename].length + ' server-requests in ' +
(Date.now() - timeStart) + ' ms');
// 6. cleanup readFileTask
delete local.readFileTaskDict[filename];
if (!fileExists) {
nextMiddleware();
}
}
};
modeNext = 0;
onNext();
};
local.middlewareError = function (error, request, response) {
/*
* this express-error-middleware will handle all unhandled requests
*/
var message;
// jslint-hack - prevent jslint from complaining about unused 'request' argument
local.nop(request);
message = '404 Not Found';
response.statusCode = 404;
if (error) {
console.error(error);
message = '500 Internal Server Error';
response.statusCode = 500;
}
try {
response.end(message);
} catch (errorCaught) {
console.error(errorCaught);
}
};
local.nop = function () {
/*
* this function will do nothing
*/
return;
};
local.runClientTest = function () {
/*
* this function will hammer the file-server with as many [client] file-request as possible,
* in 1000 ms and print the results afterwards
*/
var clientError,
clientHttpRequest,
ii,
modeNext,
onNext,
requestsPassed,
requestsTotal,
timeElapsed,
timeStart,
url;
onNext = function () {
modeNext += 1;
switch (modeNext) {
case 1:
// [client] - run initialization code
timeStart = Date.now();
clientHttpRequest = function (url) {
require('http').request(require('url').parse(url), function (response) {
response
.on('data', local.nop)
.on('end', function () {
requestsPassed += 1;
});
})
.on('error', function (error) {
clientError = clientError || error;
})
.end();
};
requestsPassed = 0;
requestsTotal = 0;
url = 'http://localhost:1337/' + require('path').basename(__filename);
// [client] - hammer server with as manny client-requests as possible in 1000 ms
// [client] - with setInterval
console.error('[client] - hammering file-server for 1000 ms with client-request ' +
JSON.stringify(url));
onNext();
break;
case 2:
setTimeout(function () {
for (ii = 0; ii < 100; ii += 1) {
clientHttpRequest(url);
requestsTotal += 1;
}
timeElapsed = Date.now() - timeStart;
// recurse / repeat this step for 1000 ms
if (timeElapsed < 1000) {
modeNext -= 1;
}
onNext();
});
break;
case 3:
// [client] - stop stress-test and wait 2000 ms
// [client] - for server to finish processing requests
timeElapsed = Date.now() - timeStart;
setTimeout(onNext, 2000);
break;
case 4:
// [server] - print result
console.error('[server] - handled ' + local.serverRequestsTotal +
' server-file-requests total');
// [client] - print result
console.error('[client] - made ' + requestsTotal + ' client-requests total in ' +
timeElapsed + ' ms');
console.error('[client] - (' + requestsPassed + ' client-requests passed)');
console.error('[client] - (' + (requestsTotal - requestsPassed) +
' client-requests failed, e.g. ' +
JSON.stringify(clientError && clientError.message) + ')');
console.error('\nfinished running stress-test\n' +
'feel free to continue playing with this file-server\n' +
'(e.g. $ curl ' + url + ')\n' +
'or press "ctrl-c" to exit');
process.exit();
break;
}
};
modeNext = 0;
onNext();
};
// [server] - create file-server with express-middlewares
local.server = require('http').createServer(function (request, response) {
var modeNextMiddleware, onNextMiddleware;
onNextMiddleware = function (error) {
if (error) {
modeNextMiddleware = Infinity;
}
modeNextMiddleware += 1;
switch (modeNextMiddleware) {
case 1:
local.middlewareFileServer(request, response, onNextMiddleware);
break;
default:
local.middlewareError(error, request, response);
break;
}
};
modeNextMiddleware = 0;
onNextMiddleware();
});
// [server] - listen on port 1337
console.error('[server] - listening on port 1337');
// [client] - run client test after server has started
local.server.listen(1337, local.runClientTest);
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment