UNPKG

fyrejet

Version:

Web Framework for node.js that strives to provide (almost) perfect compatibility with Express, while providing better performance, where you need it.

223 lines (202 loc) 6.42 kB
'use strict' const pathToRegexp = require('path-to-regexp') const methods = require('./methods') const { v4: uuidv4 } = require('uuid') module.exports = class Trouter { constructor (options) { this.opts = {} this.opts.strict = false this.opts.sensitive = options.sensitive || false this.routes = [] methods.forEach(method => { const methodUpperCase = method !== 'all' ? method.toUpperCase() : '' this[method] = this.add.bind(this, methodUpperCase) }) } exposeRoutes () { return this.routes } modifySetting (setting, value) { this.opts[setting] = value } use (route, ...fns) { let noEtag = false // 34-48 left for the sake of back-compat if (fns[0].includes('api')) { const index = fns[0].indexOf('api') fns[0].splice(index, 1) } if (fns[0].includes('propsAsFns')) { const index = fns[0].indexOf('propsAsFns') fns[0].splice(index, 1) } if (fns[0].includes('noEtag')) { const index = fns[0].indexOf('noEtag') fns[0].splice(index, 1) noEtag = true } // let's find out the number of args a function accepts let init = false if (!route || route === '*') route = '/' const handlersArgsNum = [] fns.forEach(fn => { const fnString = fn.toString() const argsNum = fnString.match(/\(\s*(.*?)\s*\)/)[1].split(', ').length handlersArgsNum.push(argsNum) }) if (fns.length === 1 && fns[0][0].init) { init = true } const starCatchAll = false const useOpts = Object.assign({}, this.opts) useOpts.end = false useOpts.strict = false const handlers = [].concat.apply([], fns) let pattern let keys = [] if (route instanceof RegExp) { pattern = route keys = 'regex' } else { pattern = pathToRegexp(route, keys, useOpts) keys = keys.map(item => item.name) keys = keys.filter(item => item !== 0) if (route && typeof route === 'string' && route !== '*') keys = routeStringPatternsTester(route, keys) } this.routes.push({ keys, pattern, path: route, method: '', handlers, handlersArgsNum, starCatchAll, middleware: true, init, noEtag, guid: uuidv4() }) return this } add (method, route, ...fns) { let noEtag = false if (fns.includes('api')) { const index = fns.indexOf('api') fns.splice(index, 1) } if (fns.includes('propsAsFns')) { const index = fns.indexOf('propsAsFns') fns.splice(index, 1) } if (fns.includes('noEtag')) { const index = fns.indexOf('noEtag') fns.splice(index, 1) noEtag = true } // let's find out the number of args a function accepts const handlersArgsNum = [] fns.forEach(fn => { const fnString = fn.toString() const argsNum = fnString.match(/\(\s*(.*?)\s*\)/)[1].split(', ').length handlersArgsNum.push(argsNum) }) let starCatchAll = false if (route === '*') { route = '/*' starCatchAll = true } const routeOpts = Object.assign({}, this.opts) routeOpts.end = true let keys = [] let pattern if (route instanceof RegExp) { pattern = route keys = 'regex' } else { pattern = pathToRegexp(route, keys, routeOpts) keys = keys.map(item => item.name) keys = keys.filter(item => item !== 0) if (route && typeof route === 'string' && route !== '*') keys = routeStringPatternsTester(route, keys) } const handlers = [].concat.apply([], fns) this.routes.push({ keys, pattern, path: route, method, handlers, handlersArgsNum, starCatchAll, noEtag, guid: uuidv4() }) return this } find (method, url, req, res) { const isHEAD = (method === 'HEAD') let i = 0; let tmp; const arr = this.routes let handlers = []; let handlersArgsNum = []; const routes = []; let noEtag = false for (; i < arr.length; i++) { tmp = arr[i] if (tmp.method.length === 0 || tmp.method === method || (isHEAD && tmp.method === 'GET')) { const test = tmp.pattern.exec(url) if (test !== null) { routes.push(tmp) if (!noEtag) { if (tmp.noEtag) noEtag = true } if (tmp.handlers.length > 1) { handlers = handlers.concat(tmp.handlers) handlersArgsNum = handlersArgsNum.concat(tmp.handlersArgsNum) } else { handlers.push(tmp.handlers[0]) handlersArgsNum.push(tmp.handlersArgsNum[0]) } } } // else not a match } const routeData = { handlers, handlersArgsNum, url, routes, noEtag } return routeData } } function routeStringPatternsTester (pat, keys) { if (!pat) pat = '' const special = ['?', '+', '*'] const regexAllSpecialChars = /(\?*\+*\**\(*\)*\[*\]*\.*)/ let includes = false for (let n = 0, o = special.length; n < o; n++) { if (pat.indexOf(special[n]) > -1) { includes = true break } } if (includes) { function specialHunt (item) { const regex = /\*|\((.*)\)/ let exec = regex.exec(item) while (exec) { keys.push(i) item = item.replace(/\*|\((.*)\)/, '') i++ exec = regex.exec(item) } } pat = pat.split('/') pat.shift() keys = [] let i = 0 pat.forEach((item) => { if (item.indexOf(':') >= 0) { const items = item.split(':') items.shift() items.forEach(item => { let includes = false let location for (let n = 0, o = special.length; n < o; n++) { location = item.indexOf(special[n]) if (location > -1) { includes = true break } } let keyName if (includes) { // location becomes cutoff location keyName = item.slice(0, location) } else { keyName = item } if (keyName[0] !== '(') { keyName = keyName.replace(new RegExp(regexAllSpecialChars.source, regexAllSpecialChars.flags + 'gi'), '') // ugly regex to remove special characters from param name keys.push(keyName) } if (item.search(/(\?|\+|\*)/) >= 0) { return specialHunt(item) } }) return } if (item.search(/(\?|\+|\*)/) >= 0) { return specialHunt(item) } }) } return keys }