Skip to content

Instantly share code, notes, and snippets.

@lensanag
Created November 28, 2020 20:03
Show Gist options
  • Save lensanag/8371f65a9fb10dbca53ef1442281246d to your computer and use it in GitHub Desktop.
Save lensanag/8371f65a9fb10dbca53ef1442281246d to your computer and use it in GitHub Desktop.
simple "regex router" & node http server
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,
};
};
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