Created
April 25, 2018 12:42
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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