time-to-first-request/index.js (99 lines of code) (raw):

// This can be used to measure the time to first request, // including starting up our Docker container: // npm install // node index.js // // And in a different terminal: // time curl -s http://localhost:9000/sling/chouc/route // // This proxy starts the container on the first request // (for now, you need to stop it before running this utility) // so the reported time is a realistic "time to first request" // value. // // The "docker events" command also provides useful information // in terms of container startup time. // const http = require('http'); const httpProxy = require('http-proxy'); const Docker = require('dockerode'); const waitOn = require('wait-on'); const elapsedTime = require('elapsed-time') const yargs = require('yargs') const argv = yargs .usage('$0 <cmd> [args]') .help() .option('listen', { default: 9000, describe: 'port on which to listen' }) .option('waitOn', { default: '/index.html', describe: 'path for testing server readiness' }) .option('image', { default: 'httpd:2.4.39-alpine', describe: 'Docker image to start (get it first with docker pull)' }) .option('dockerHostPort', { default: 8080, describe: 'Docker host port for our container' }) .option('dockerContainerPort', { default: 80, describe: 'Port exposed by our image' }) .argv const listenPort = argv.listen; const targetUrl = `http://127.0.0.1:${argv.dockerHostPort}`; const waitUrl = `${targetUrl}/${argv.waitOn}`; const dockerImage = argv.image; const dockerStartOptions = {}; dockerStartOptions['PortBindings'] = {} dockerStartOptions['PortBindings'][`${argv.dockerContainerPort}/tcp`] = [{ "HostIP":"0.0.0.0", "HostPort": `${argv.dockerHostPort}` }]; const proxy = httpProxy.createProxyServer({}); const waitOpts = { resources: [ waitUrl ], delay: 0, interval: 10, timeout: 30000, window: 1, }; const docker = new Docker(); const actions = {}; const getContainer = (async imageName => { const containers = await docker.listContainers(); return containers.find(container => { return container.Image == imageName; }); }); var server = http.createServer(async (req, res) => { const et = elapsedTime.new().start(); existingContainer = await getContainer(dockerImage); if(existingContainer) { console.log(`Container is already running: ${dockerImage}(${et.getValue()})`); } else { actions.startedContainer = true; console.log(`Starting container: ${dockerImage}(${et.getValue()})`); docker.run(dockerImage, null, null, dockerStartOptions); } // No need to wait for async call, just wait for our URL console.log(`Waiting on ${waitOpts.resources[0]} (${et.getValue()})`); waitOn(waitOpts).then(() =>{ console.log(`Time to wait for ${waitUrl}: (${et.getValue()})`); console.log(`Proxying ${req.url} (${et.getValue()})`); proxy.web(req, res, { target: targetUrl }); console.log(`Done proxying (${et.getValue()})`); }); }); const cleanup = async () => { if(!actions.startedContainer) { console.log('Did not start container, nothing to cleanup'); } else { const runningContainer = await getContainer(dockerImage); if(runningContainer) { console.log(`Killing container ${dockerImage}/${runningContainer.Id.substring(0,12)} ...`); const container = await docker.getContainer(runningContainer.Id); await container.kill(); console.log('killed'); } } process.exit(); } [ 'SIGINT', 'SIGTERM', ].forEach(signal => { process.on(signal, cleanup); }) console.log(`listening on port ${listenPort}, proxying to ${targetUrl} with ${dockerImage} on port ${argv.dockerHostPort}/${argv.dockerContainerPort}`); server.listen(listenPort)