Created
November 28, 2020 20:03
-
-
Save lensanag/8371f65a9fb10dbca53ef1442281246d to your computer and use it in GitHub Desktop.
simple "regex router" & node http server
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
const notFoundHandler = (_0, _1, res) => (res.writeHead(404, {}) && res.end()); | |
let _defaultHandler; | |
let _context; | |
const tmpHandlers = { | |
'GET': [], | |
'POST': [], | |
'PUT': [], | |
'DELETE': [], | |
'PATCH': [], | |
}; | |
const finalHandlers = { | |
'GET': [], | |
'POST': [], | |
'PUT': [], | |
'DELETE': [], | |
'PATCH': [], | |
}; | |
/** | |
* Agrega segun el metodo al array 'GET' una tupla(array de 2) la expresion regular y el manejador en caso de match. | |
* @param {RegExp} regex expresion regular a coincidir | |
* @param {Function} handler funcion que manejara la petición | |
*/ | |
const _get = async (regex, handler) => { | |
tmpHandlers['GET'].push([regex, handler]); | |
}; | |
/** | |
* Agrega segun el metodo al array 'POST' una tupla(array de 2) la expresion regular y el manejador en caso de match. | |
* @param {RegExp} regex expresion regular a coincidir | |
* @param {Function} handler funcion que manejara la petición | |
*/ | |
const _post = async (regex, handler) => { | |
tmpHandlers['POST'].push([regex, handler]); | |
}; | |
/** | |
* Agrega segun el metodo al array 'PUT' una tupla(array de 2) la expresion regular y el manejador en caso de match. | |
* @param {RegExp} regex expresion regular a coincidir | |
* @param {Function} handler funcion que manejara la petición | |
*/ | |
const _put = async (regex, handler) => { | |
tmpHandlers['PUT'].push([regex, handler]); | |
}; | |
const _patch = async (regex, handler) => { | |
tmpHandlers['PATCH'].push([regex, handler]); | |
}; | |
/** | |
* Agrega segun el metodo al array 'DELETE' una tupla(array de 2) la expresion regular y el manejador en caso de match. | |
* @param {RegExp} regex expresion regular a coincidir | |
* @param {Function} handler funcion que manejara la petición | |
*/ | |
const _delete = async (regex, handler) => { | |
tmpHandlers['DELETE'].push([regex, handler]); | |
}; | |
const mapFunctionMethods = { | |
show: _get, | |
detail: _get, | |
store: _post, | |
edit: _put, | |
destroy: _delete, | |
}; | |
/** | |
* @type Array | |
*/ | |
const allowedFunctions = Object.keys(mapFunctionMethods); | |
/** | |
* | |
* @param {RegExp} regex expresion regular de la ruta | |
* @param {Object} handlers contiene los metodos permitidos en la ruta | |
* @param {Function} handlers.show manejador para el metodo get | |
* @param {Function} handlers.detail manejador para el metodo get | |
* @param {Function} handlers.store manejador para el metodo post | |
* @param {Function} handlers.edit manejador para el metodo put | |
* @param {Function} handlers.destroy manejador para el metodo delete | |
*/ | |
const _resource = async (regex, handlers) => { | |
const allow = Object.keys(handlers).filter(el => allowedFunctions.includes(el)); | |
const promiseArr = []; | |
for (let idx = 0; idx < allow.length; idx++) { | |
const el = allow[idx]; | |
promiseArr.push(mapFunctionMethods[el](regex, handlers[el])); | |
} | |
return Promise.all(promiseArr); | |
} | |
/** | |
* apila los manejadores | |
* @param {Array} middleware | |
*/ | |
const toHandlersStack = (middleware) => { | |
for (const key in tmpHandlers) { | |
const el = tmpHandlers[key]; | |
for (let idx = 0; idx < el.length; idx++) { | |
const pair = el[idx]; | |
finalHandlers[key].push([pair[0], [pair[1], ...middleware.reverse()]]); | |
} | |
el.splice(0, el.length); | |
} | |
}; | |
/** | |
* | |
* @param {Array[Promise]} callback | |
* @param {Array} middleware | |
*/ | |
const _group = async (routes, middleware) => { | |
Promise.all(routes) | |
.then(() => { | |
toHandlersStack(middleware||[]); | |
}); | |
}; | |
const json = function (status, data) { | |
this.writeHead(status, {'Content-Type': 'application/json'}); | |
return this.end(JSON.stringify(data)); | |
}; | |
const _dispatch = (request, response) => { | |
/** | |
* @type Array | |
*/ | |
let stack; | |
/** | |
* @type number | |
*/ | |
let pointer; | |
/** | |
* funcion recursiva que recorre la pila de manejadores (middleware y handler especifico) | |
*/ | |
const stackCaller = () => { | |
if (--pointer) return stack[pointer].call(null, request, response, stackCaller); | |
return stack[pointer].call(null, request, response); | |
}; | |
/** | |
* @type Object | |
*/ | |
let match; | |
response.json = json; | |
request.context = _context; | |
const methodHandlers = finalHandlers[request.method]; | |
for (let idx = methodHandlers.length - 1; idx >= 0; idx--) { | |
const [regex, currentStack] = methodHandlers[idx]; | |
if (match = regex.exec(request.url)) { | |
request.searchParams = (new URL(request.headers.host + request.url)).searchParams || {}; | |
request.routeParams = match.groups || {}; | |
if(currentStack.length === 1) return currentStack[0].call(null, request, response); | |
pointer = currentStack.length; | |
stack = currentStack; | |
return stackCaller(); | |
} | |
} | |
return _defaultHandler.call(null, request, response); | |
}; | |
exports.Router = function Router(context, defaultHandler) { | |
_defaultHandler = defaultHandler || notFoundHandler; | |
_context = context || {}; | |
return { | |
get: _get, | |
post: _post, | |
put: _put, | |
delete: _delete, | |
patch: _patch, | |
resource: _resource, | |
group: _group, | |
dispatch: _dispatch, | |
}; | |
}; |
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
require('dotenv').config(); | |
const http = require('http'); | |
const port = process.env.PORT; | |
const {Router} = require('./regex-router.js'); | |
const R = new Router(null, (ask, reply) => { | |
console.log('NOT FOUND'); | |
return reply.json(404, 'NOT FOUND'); | |
}); | |
R.group([ | |
R.resource(/\/books\/(?<id>.+)/, | |
{ | |
detail: (ask, reply) => { | |
reply.json(200, {msg: `from resource detail`, id: ask.routeParams.id}); | |
}, | |
edit: (ask, reply) => { | |
reply.json(200, {msg: `from resource edit`, id: ask.routeParams.id}); | |
}, | |
store: (ask, reply) => { | |
reply.json(200, {msg: `from resource store`, id: ask.routeParams.id}); | |
}, | |
destroy: (ask, reply) => { | |
reply.json(200, {msg: `from resource destroy`, id: ask.routeParams.id}); | |
}, | |
}), | |
R.resource(/\/books\/one\/(?<id>.+)/, | |
{ | |
detail: (ask, reply) => { | |
reply.json(200, {msg: `from resource detail`, id: ask.routeParams.id}); | |
}, | |
edit: (ask, reply) => { | |
reply.json(200, {msg: `from resource edit`, id: ask.routeParams.id}); | |
}, | |
store: (ask, reply) => { | |
reply.json(200, {msg: `from resource store`, id: ask.routeParams.id}); | |
}, | |
destroy: (ask, reply) => { | |
reply.json(200, {msg: `from resource destroy`, id: ask.routeParams.id}); | |
}, | |
}), | |
R.resource(/\/books\/none\/(?<id>.+)/, | |
{ | |
detail: (ask, reply) => { | |
reply.json(200, {msg: `from resource detail`, id: ask.routeParams.id}); | |
}, | |
edit: (ask, reply) => { | |
reply.json(200, {msg: `from resource edit`, id: ask.routeParams.id}); | |
}, | |
store: (ask, reply) => { | |
reply.json(200, {msg: `from resource store`, id: ask.routeParams.id}); | |
}, | |
destroy: (ask, reply) => { | |
reply.json(200, {msg: `from resource destroy`, id: ask.routeParams.id}); | |
}, | |
}), | |
]); | |
const server = http.createServer((request, response) => { | |
return R.dispatch(request, response); | |
}); | |
server.listen(port, () => { | |
console.log(`server router listen on port: ${port} 🚀`); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment