├── .gitignore ├── readme.md ├── slow ├── package.json ├── index.js └── util.js └── fast ├── package.json ├── index.js └── util.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Keeping Node.js Fast 2 | ## Tools, techniques and tips for making high performance Node.js servers 3 | 4 | This repository contains source code for the Smashing Magazine Article, 5 | which can be found here: 6 | 7 | https://www.smashingmagazine.com/2018/06/nodejs-tools-techniques-performance-servers/ 8 | -------------------------------------------------------------------------------- /slow/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slow-to-fast", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "MIT", 12 | "dependencies": { 13 | "restify": "^7.1.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /fast/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slow-to-fast", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "MIT", 12 | "dependencies": { 13 | "restify": "^7.1.0", 14 | "xxhash": "^0.2.4" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /slow/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const restify = require('restify') 4 | const { etagger, timestamp, fetchContent } = require('./util')() 5 | const server = restify.createServer() 6 | 7 | server.use(etagger().bind(server)) 8 | 9 | server.get('/seed/v1', function (req, res, next) { 10 | fetchContent(req.url, (err, content) => { 11 | if (err) return next(err) 12 | res.send({data: content, url: req.url, ts: timestamp()}) 13 | next() 14 | }) 15 | }) 16 | 17 | server.listen(3000) 18 | 19 | -------------------------------------------------------------------------------- /fast/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const restify = require('restify') 4 | const { etagger, timestamp, fetchContent } = require('./util')() 5 | const server = restify.createServer() 6 | 7 | server.use(etagger().bind(server)) 8 | 9 | server.get('/seed/v1', function (req, res, next) { 10 | fetchContent(req.url, (err, content) => { 11 | if (err) return next(err) 12 | const ts = timestamp() 13 | req.id(ts.toString()) 14 | res.end(`{"data": "${content}", "url": "${req.url}", "ts": ${ts}}`) 15 | next() 16 | }) 17 | }) 18 | 19 | server.listen(3000) 20 | 21 | -------------------------------------------------------------------------------- /fast/util.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const crypto = require('crypto') 4 | 5 | module.exports = () => { 6 | const content = crypto.rng(5000).toString('hex') 7 | const ONE_MINUTE = 60000 8 | var last = Date.now() 9 | 10 | function timestamp () { 11 | var now = Date.now() 12 | if (now - last >= ONE_MINUTE) last = now 13 | return last 14 | } 15 | 16 | function etagger () { 17 | var cache = {} 18 | var afterEventAttached = false 19 | function attachAfterEvent (server) { 20 | if (afterEventAttached === true) return 21 | afterEventAttached = true 22 | server.on('after', (req, res) => { 23 | if (res.statusCode !== 200) return 24 | const key = req.url 25 | const etag = req.id() 26 | if (cache[key] !== etag) cache[key] = etag 27 | }) 28 | } 29 | return function (req, res, next) { 30 | attachAfterEvent(this) 31 | const key = req.url 32 | if (key in cache) res.set('Etag', cache[key]) 33 | res.set('Cache-Control', 'public, max-age=120') 34 | next() 35 | } 36 | } 37 | 38 | function fetchContent (url, cb) { 39 | setImmediate(() => { 40 | if (url !== '/seed/v1') cb(Object.assign(Error('Not Found'), {statusCode: 404})) 41 | else cb(null, content) 42 | }) 43 | } 44 | 45 | return { timestamp, etagger, fetchContent } 46 | 47 | } -------------------------------------------------------------------------------- /slow/util.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const crypto = require('crypto') 4 | 5 | module.exports = () => { 6 | const content = crypto.rng(5000).toString('hex') 7 | const ONE_MINUTE = 60000 8 | var last = Date.now() 9 | 10 | function timestamp () { 11 | var now = Date.now() 12 | if (now - last >= ONE_MINUTE) last = now 13 | return last 14 | } 15 | 16 | function etagger () { 17 | var cache = {} 18 | var afterEventAttached = false 19 | function attachAfterEvent (server) { 20 | if (afterEventAttached === true) return 21 | afterEventAttached = true 22 | server.on('after', (req, res) => { 23 | if (res.statusCode !== 200) return 24 | if (!res._body) return 25 | const key = crypto.createHash('sha512') 26 | .update(req.url) 27 | .digest() 28 | .toString('hex') 29 | const etag = crypto.createHash('sha512') 30 | .update(JSON.stringify(res._body)) 31 | .digest() 32 | .toString('hex') 33 | if (cache[key] !== etag) cache[key] = etag 34 | }) 35 | } 36 | return function (req, res, next) { 37 | attachAfterEvent(this) 38 | const key = crypto.createHash('sha512') 39 | .update(req.url) 40 | .digest() 41 | .toString('hex') 42 | if (key in cache) res.set('Etag', cache[key]) 43 | res.set('Cache-Control', 'public, max-age=120') 44 | next() 45 | } 46 | } 47 | 48 | function fetchContent (url, cb) { 49 | setImmediate(() => { 50 | if (url !== '/seed/v1') cb(Object.assign(Error('Not Found'), {statusCode: 404})) 51 | else cb(null, content) 52 | }) 53 | } 54 | 55 | return { timestamp, etagger, fetchContent } 56 | 57 | } --------------------------------------------------------------------------------