Last active
September 7, 2023 18:38
-
-
Save f5io/5d029b397f39a37370eeae3eb3e800cb to your computer and use it in GitHub Desktop.
An approach to async middleware for raw `http` in Node.
This file contains 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
const http = require('http'); | |
const methods = [ 'get', 'put', 'post', 'delete', 'head' ]; | |
const isStream = obj => | |
obj && | |
typeof obj === 'object' && | |
typeof obj.pipe === 'function'; | |
const isReadable = obj => | |
isStream(obj) && | |
typeof obj._read === 'function' && | |
typeof obj._readableState === 'object'; | |
const isNext = Symbol('isNext'); | |
const composeWithContext = (...mw) => (...args) => | |
new Promise(async (resolve, reject) => { | |
const nxt = args[args.length - 1][isNext] ? args.pop() : () => resolve(); | |
await mw.reduceRight((next, curr) => | |
async function() { | |
next[isNext] = true; | |
try { await curr(...args.concat(next)) } | |
catch(e) { reject(e); throw e; } | |
}, nxt)(); | |
resolve(); | |
}); | |
const resolve = async (req, res, next) => { | |
await next(); | |
if (isReadable(res.body)) { | |
res.body.pipe(res); | |
} else { | |
res.end(res.body || ''); | |
} | |
}; | |
const route = (path, fn) => async (req, res, next) => { | |
if (req.url === path) { | |
await composeWithContext(fn, next)(req, res); | |
} else { | |
await next(); | |
} | |
}; | |
const method = (me, fn) => async(req, res, next) => { | |
if (req.method.toLowerCase() === me) { | |
await composeWithContext(fn, next)(req, res); | |
} else { | |
await next(); | |
} | |
}; | |
const nomad = () => { | |
const mw = [ resolve ]; | |
const app = { | |
use: (path, fn) => { | |
if (!fn && typeof path === 'function') return (mw.push(path), app); | |
if (typeof path === 'string' && typeof fn === 'function') return (mw.push(route(path, fn)), app); | |
}, | |
listen: (port, callback = () => {}) => { | |
http.createServer(composeWithContext(...mw)).listen(port, callback); | |
} | |
}; | |
return methods.reduce((a, me) => { | |
a[me] = (path, fn) => { | |
if (!fn && typeof path === 'function') return (mw.push(method(me, path)), app); | |
if (typeof path === 'string' && typeof fn === 'function') return (mw.push(route(path, method(me, fn))), app); | |
} | |
return a; | |
}, app); | |
}; | |
export default nomad; |
This file contains 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
import fs from 'fs'; | |
import nomad from './core'; | |
const PORT = 1337; | |
let count = 0; | |
const stats = file => | |
new Promise((resolve, reject) => { | |
fs.stat(file, (err, stat) => err ? reject(err) : resolve(stat)); | |
}); | |
const assets = path => async (req, res, next) => { | |
try { | |
const filePath = `${path}${req.url}`; | |
const stat = await stats(filePath); | |
if (!stat.isFile()) throw new Error('Cannot load directory'); | |
res.statusCode = 200; | |
res.body = fs.createReadStream(filePath); | |
} catch(e) { | |
await next(); | |
} | |
} | |
const app = nomad(); | |
app.use(async (req, res, next) => { | |
const time = Date.now(); | |
console.log(`initiate req ${++count}`); | |
await next(); | |
res.setHeader('Response-Time', `${Date.now() - time}ms`); | |
}); | |
app.use(async (req, res, next) => { | |
try { await next() } | |
catch(e) { | |
res.statusCode = 500; | |
res.body = e.message; | |
} | |
}); | |
app.get(assets(process.cwd())); | |
app.use('/hello', (req, res, next) => { | |
res.statusCode = 200; | |
res.body = 'hello'; | |
}); | |
app.use('/world', (req, res, next) => { | |
res.statusCode = 200; | |
res.body = 'world'; | |
}); | |
app.get('/file', (req, res, next) => { | |
res.statusCode = 200; | |
res.body = fs.createReadStream(`${process.cwd()}/package.json`); | |
}); | |
app.use(() => { | |
throw new Error('Route not found.'); | |
}); | |
app.listen(PORT, () => console.log(`Server started on: ${PORT}`)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment