Compare commits

..

No commits in common. "c4f59fbb0150af710e5714d2d4dd2454d70869d7" and "2e1dadbdeaa33b63aa2a0ee947eaf32d848315cd" have entirely different histories.

13 changed files with 462 additions and 4243 deletions

View file

@ -1,10 +1,9 @@
import { summary, run, bench } from 'mitata';
import { createRouter, insertItem } from '@mapl/router'
import { compileRouter } from './mapl_compiler.mjs'
import assert from 'assert'
import { compilePaths } from "./router_v2.mjs"
import { compilePaths as mainCompiler } from "../router_v2.mjs"
import { FlaskaRouter as FlaskaRouterBuffer } from "../flaska_buffer.mjs"
import { FlaskaRouter as FlaskaRouterFast } from "../flaska_fast.mjs"
import { compilePaths as mainCompiler, compilePathsClosure } from "../router_v2.mjs"
import * as consts from './const.js'
function printCurrentStatus(fn) {
@ -37,21 +36,13 @@ function printStatusHelperText() {
bench('noop', () => { });
bench('noop2', () => { });
let paths = consts.allRoutes.map(x => ({ path: x }))
let paths = consts.allManyRoutes.map(x => ({ path: x }))
let tests = paths.map(p => ([p.path.replace(/:[^/]+/g, '_'), p]))
let testStrings = tests.map(x => x[0])
let testStringsMapl = testStrings.map(x => x.slice(1))
let func = [[testStrings, mainCompiler(paths)]]
let flaskaRouterBuffer = new FlaskaRouterBuffer()
flaskaRouterBuffer.paths = paths.slice()
flaskaRouterBuffer.compile()
func.push([testStrings, flaskaRouterBuffer.match])
let flaskaRouterFast = new FlaskaRouterFast()
flaskaRouterFast.paths = paths.slice()
flaskaRouterFast.compile()
func.push([testStrings, flaskaRouterFast.match])
let func = [[testStrings, ...mainCompiler(paths)]]
func.push([testStrings, ...compilePathsClosure(paths)])
let maplPaths = paths.map(x => x.path.replace(/::[^\/]+/g, '**').replace(/:[^\/]+/g, '*'))
const maplRouter = createRouter();
@ -60,15 +51,15 @@ for (let route of maplPaths) {
}
let maplMatcher = compileRouter(maplRouter)
func.push([testStringsMapl, maplMatcher])
func.push([testStringsMapl, maplMatcher, maplMatcher])
for (let [tests, fun] of func) {
for (let [tests, _, fun] of func) {
console.log(`--- warming up ${fun.name || 'mapl'} ---`)
for (var i = 0; i < 100000; i++) {
for (var i = 0; i < 10000; i++) {
tests.forEach(fun)
}
}
for (let [tests, fun] of func) {
for (let [tests, _, fun] of func) {
console.log(`--- Sanity checking ${fun.name || 'mapl'} ---`)
for (let a of tests) {
// console.log(a, fun(a))
@ -82,9 +73,9 @@ for (let [_, org] of func) {
printStatusHelperText()
summary(() => {
func.forEach(function([tests, fun]) {
func.forEach(function([tests, _, fun]) {
// console.log(tests, fun, tests.map(fun))
bench(fun.name || 'mapl', function() {
bench((fun.name || 'bound mapl').slice(6), function() {
return tests.map(fun)
})
})

View file

@ -1,117 +0,0 @@
import { summary, run, bench } from 'mitata';
// Warmup (de-optimize `bench()` calls)
bench('noop', () => { });
bench('noop2', () => { });
function padStart(length) {
return ''.padStart(length * 2)
}
const data = [
'/',
'/api/articles',
'/api/articles/:id/file',
'/api/articles/:id',
'/api/articles/public',
'/api/articles/public/:id',
'/api/categories',
'/api/categories/:categoryId/products',
'/api/categories/:categoryId/properties',
'/api/categories/:categoryId/values/:props',
'/api/categories/:categoryId',
//'/api/categories/:categoryId/products/:productId',
'/api/categories/:categoryId/products/:productId',
'/api/customers',
'/api/customers/:id',
'/api/customers/kennitala/:kennitala',
'/api/customers/public/kennitala/:kennitala',
'/api/customers/search/:search',
'/api/file',
'/api/file/:id',
'/api/media',
'/api/media/:id',
'/api/orderitem',
'/api/orderitem/:id',
'/api/orders',
'/api/orders/:orderId',
'/api/orders/:orderId/sell',
'/api/pages',
'/api/pages/:pageId',
'/api/pages/:pageId/articles',
'/api/pages/:pageId/articles/public',
'/api/products',
'/api/products/:id',
'/api/products/:id/movement',
'/api/products/:id/sub_products/:productId',
//'/api/products/:id/sub_products/:productId',
'/api/products/code/:code',
'/api/products/property/:propertyId',
'/api/properties',
'/api/properties/:id',
'/api/sales',
'/api/sales/:id',
'/api/stockitem',
'/api/stockitem/:id',
'/api/stocks',
'/api/stocks/:id',
'/api/stocks/:id/commit',
'/api/test',
'/api/test/auth',
'/api/test/error',
'/api/workentries',
'/api/workentries/:id',
'/api/works',
'/api/works/:id',
'/api/works/:id/lock',
'/api/works/public',
'/api/staff',
'/api/staff/:id',
'/::rest',
]
function arrIncludes(data) {
let out = new Array()
for (let item of data) {
if (out.includes(item)) { break }
out.push(item)
}
return out
}
function setAdd(data) {
let s = new Set()
for (let item of data) {
let size = s.size
if (s.add(item).size === size) { break }
}
return s
}
let func = [arrIncludes, setAdd];
for (let fun of func) {
console.log(`--- warming up ${fun.name || 'mapl'} ---`)
for (var i = 0; i < 100; i++) {
fun(data)
}
}
await new Promise(res => setTimeout(res, 3000))
summary(() => {
for (let i = 20; i >= 0; i--) {
const dataSet = data.slice(0, data.length - i)
func.forEach(function(fun) {
bench(`${dataSet.length} items: ${fun.name}`, function() {
return fun(dataSet)
})
})
}
// console.log(tests, fun, tests.map(fun))
/*bench(fun.name, function() {
return fun(data)
})*/
})
run();

View file

@ -37,6 +37,16 @@ export const MimeTypeDb = getDb()
* Router
*/
class Branch {
constructor() {
this.children = new Map()
this.paramName = null
this.fullparamName = null
this.handler = null
this.middlewares = []
}
}
export const ErrorCodes = {
ERR_CONNECTION_ABORTED: 'ERR_CON_ABORTED'
}
@ -104,9 +114,9 @@ export function JsonHandler(org = {}) {
ctx.req.body = {}
return
}
const data = Buffer.concat(buffers).toString();
try {
ctx.req.body = JSON.parse(data)
} catch (err) {
@ -135,7 +145,7 @@ export function CorsHandler(opts = {}) {
// Always add vary header on origin. Prevent caches from
// accidentally caching wrong preflight request
ctx.headers['Vary'] = 'Origin'
// Set status to 204 if OPTIONS. Just handy for flaska and
// other checking.
if (ctx.method === 'OPTIONS') {
@ -218,7 +228,7 @@ export function FormidableHandler(formidable, org = {}) {
// For testing/stubbing purposes
let rename = formidable.fsRename || fs.rename
return function(ctx) {
let form = formidable.IncomingForm()
form.uploadDir = opts.uploadDir
@ -253,7 +263,7 @@ export function FormidableHandler(formidable, org = {}) {
keys.map(key => {
let filename
let target
try {
filename = opts.filename(ctx.req.files[key]) || ctx.req.files[key].name
target = path.join(opts.uploadDir, filename)
@ -376,7 +386,7 @@ export class FileResponse {
}
}
}
let ext = path.extname(this.filepath).slice(1)
let found = MimeTypeDb[ext]
if (found) {
@ -393,312 +403,190 @@ export class FileResponse {
}
}
/*
* --- Router ---
*/
class RouterError extends Error {
constructor(route1, route2, ...params) {
// Pass remaining arguments (including vendor specific ones) to parent constructor
super(...params);
// Maintains proper stack trace for where our error was thrown (only available on V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, RouterError);
}
this.name = "RouterError";
this.routeA = route1
this.routeB = route2
}
}
function Child(split, x, i) {
this.path = null
this.isParams = split[x].isParams ? split[x].word : null
this.isFullParams = split[x].isFullParams ? split[x].word : null
this.paramVarName = split[x].paramVarName ?? null
this.char = !this.isParams && !this.isFullParams ? split[x].word[i] || '/' : null
this.count = 0
this.children = []
}
const regParamPrefix = /^::?/
const regCleanNonAschii = /(?![a-zA-Z_])./g
const regCleanRest = /_+/g
const regStarDoubleParam = /::[^:/]+/g
const regStarSingleParam = /:[^:/]+/g
const SlashCode = '/'.charCodeAt(0)
const spaces = ' '
export class FlaskaRouter {
constructor() {
this.paths = []
this.registeredPaths = new Set()
this.root = new Branch()
}
addRoute(path, middlewares, orgHandler) {
if (path[0] !== '/')
throw new RouterError(null, null, `addRoute("${path}") path must start with forward slash`)
let cleaned = path
if (cleaned.indexOf('/:') >= 0) {
cleaned = cleaned.replace(regStarDoubleParam, '**').replace(regStarSingleParam, '*')
if (cleaned.indexOf(':') > 0) {
throw new RouterError(null, null, `addRoute("${path}") path has missing name or word between two forward slashes`)
}
if (cleaned.indexOf('**/') > 0) {
throw new RouterError(null, null, `addRoute("${path}") cannot add anything after a full param route`)
addRoute(route, orgMiddlewares, orgHandler) {
if (route[0] !== '/')
throw new Error(`route "${route}" must start with forward slash`)
let middlewares = orgMiddlewares
let handler = orgHandler
if (!orgHandler) {
handler = orgMiddlewares
middlewares = []
}
if (middlewares && typeof(middlewares) === 'function') {
middlewares = [middlewares]
}
assertIsHandler(handler, 'addRoute()')
let start = 1
let end = 1
let name = ''
let param = ''
let isParam = false
let isFullParam = false
let branch = this.root
if (route.indexOf(':') < 0 && false) {
let name = route
if (name.length > 1 && name[name.length - 1] === '/') {
name = name.slice(0, -1)
}
let child = new Branch()
branch.children.set(name, child)
child.handler = handler
child.middlewares = middlewares
}
if (cleaned.indexOf('//') >= 0) {
throw new RouterError(null, null, `addRoute("${path}") path has missing name or word between two forward slashes`)
}
let size = this.registeredPaths.size
if (this.registeredPaths.add(cleaned).size === size) {
throw new RouterError(null, null, `addRoute("${path}") found an existing route with same path of ${cleaned}`)
}
let handlers = []
if (Array.isArray(middlewares)) {
handlers.push(...middlewares)
if (typeof(orgHandler) !== 'function') {
throw new RouterError(orgHandler, null, `addRoute("${path}") was called with a handler that was not a function`)
}
} else {
handlers.push(middlewares)
}
if (orgHandler) {
handlers.push(orgHandler)
}
for (let handler of handlers) {
if (typeof(handler) !== 'function') {
throw new RouterError(handler, null, `addRoute("${path}") was called with a handler that was not a function`)
}
}
this.paths.push({
path,
handlers
})
}
__buildChild(x, i, splitPaths) {
let splitPath = splitPaths[0]
let letter = new Child(splitPath.split, x, i)
let consume = []
if (splitPath.split.length === x + 1
&& (splitPath.split[x].isParams
|| splitPath.split[x].isFullParams
|| splitPath.split[x].word.length === i + 1)) {
letter.path = splitPath.entry
letter.count += 1
} else {
consume = [splitPath]
}
for (let y = 1; y < splitPaths.length; y++) {
let checkPath = splitPaths[y]
if (!checkPath.split[x]
|| checkPath.split[x].isParams !== splitPath.split[x].isParams
|| checkPath.split[x].isFullParams !== splitPath.split[x].isFullParams
|| !checkPath.split[x].isParams
&& !checkPath.split[x].isFullParams
&& (checkPath.split[x].word[i] || '/') !== letter.char) break
consume.push(checkPath)
}
letter.count += consume.length
if (splitPath.split[x].word.length === i || splitPath.split[x].isParams || splitPath.split[x].isFullParams) {
x++
i = -1
}
while (consume.length) {
letter.children.push(this.__buildChild(x, i + 1, consume))
consume.splice(0, letter.children[letter.children.length - 1].count)
}
return letter
}
__buildTree(splitPaths) {
let builder = []
while (splitPaths.length) {
builder.push(this.__buildChild(0, 0, splitPaths))
splitPaths.splice(0, builder[builder.length - 1].count)
}
return builder
}
__splitAndSortPaths(paths, separateStatic = true) {
let staticPaths = new Map()
let paramsPaths = []
let collator = new Intl.Collator('en', { sensitivity: 'accent' });
paths.forEach(function(entry) {
if (entry.path[0] !== '/') throw new RouterError(entry, null, 'Specified route was missing forward slash at start')
// Collect static paths separately
if (entry.path.indexOf('/:') < 0 && separateStatic) {
return staticPaths.set(entry.path, {
path: entry,
params: {}
})
}
// Collect params path separately
paramsPaths.push({
split: entry.path.slice(1).split(/\//g).map(function(word) {
let actualWord = word.replace(regParamPrefix, '')
return {
word: actualWord,
isParams: word[0] === ':' && word[1] !== ':',
isFullParams: word[0] === ':' && word[1] === ':',
paramVarName: word[0] === ':'
? actualWord.replace(regCleanNonAschii, '_').replace(regCleanRest, '_')
: null
for (let i = 1; i <= route.length; i++) {
if ((i === route.length || route[i] === '/') && end > start) {
if (branch.fullparamName) {
throw new Error(`route "${route}" conflicts with a sub-branch that has a full param child`)
}
let child
name = route.substring(start, end)
if (isFullParam) {
param = name
name = __fullParamMapName
} else if (isParam) {
param = name
name = __paramMapName
}
if (branch.children.has(name)) {
child = branch.children.get(name)
}
else if (isParam && !isFullParam && branch.children.has(__fullParamMapName)) {
throw new Error(`route "${route}" conflicts with a sub-branch that has a full param child`)
}
else if (isFullParam && branch.children.has(__paramMapName)) {
throw new Error(`route "${route}" conflicts with a sub-branch that has a partial param child`)
}
else {
child = new Branch()
branch.children.set(name, child)
}
branch = child
end = i
start = i
if (isParam) {
if (branch.paramName && branch.paramName !== param) {
throw new Error(`route "${route}" conflicts with pre-existing param name of ${branch.paramName} instead of ${param}`)
}
}),
entry,
})
})
paramsPaths.sort(function(aGroup, bGroup) {
let length = Math.max(aGroup.split.length, bGroup.split.length)
for (let x = 0; x < length; x++) {
let a = aGroup.split[x]
let b = bGroup.split[x]
if (!a) return -1
if (!b) return 1
// Full params go last
if (a.isFullParams && b.isFullParams) throw new RouterError(aGroup.entry, bGroup.entry, 'Two full path routes found on same level')
if (a.isFullParams) return 1
if (b.isFullParams) return -1
// Params go second last
if (a.isParams && !b.isParams) return 1
if (!a.isParams && b.isParams) return -1
// otherwise sort alphabetically if not identical
if (a.word !== b.word) return collator.compare(a.word, b.word)
if (isFullParam) {
branch.fullparamName = param
} else {
branch.paramName = param
}
isParam = false
}
} else if (route[i] === '/' && end === start) {
throw new Error(`route "${route}" has missing path name inbetween slashes`)
}
throw new RouterError(aGroup, bGroup, 'Two identical paths were found')
})
return {
staticPaths,
paramsPaths,
}
}
__getIndex(offset, additions, params) {
return (offset + additions)
+ (params.length
? ' + ' + params.map(a => `offset${a[1]}`).join(' + ')
: '')
}
__treeIntoCompiledCodeReturnPath(indentString, paths, branch, params) {
let pathIndex = paths.indexOf(branch.path)
if (pathIndex < 0) {
throw new RouterError(branch.path, null, 'InternalError: Specified path was not found in paths')
}
let output = '\n' + indentString + `return {`
output += '\n' + indentString + ` path: paths[${pathIndex}],`
if (params.length) {
output += '\n' + indentString + ` params: {`
for (let param of params) {
output += '\n' + indentString + ` ${param[0]}: s${param[1]},`
if (i === route.length) {
branch.handler = handler
branch.middlewares = middlewares
continue
}
if (route[i] === ':') {
if (isParam) {
isFullParam = true
}
isParam = true
end = start = i + 1
}
else if (route[i] === '/') {
end = start = i + 1
}
else {
end++
}
output += '\n' + indentString + ` },`
} else {
output += '\n' + indentString + ` params: {},`
}
output += '\n' + indentString + `}`
return output
}
__treeIntoCompiledCodeBranch(paths, branches, indent = 0, params = []) {
let output = ''
let indentation = spaces.slice(0, (indent - params.length) * 2)
let addEndBracket = true
for (let i = 0; i < branches.length; i++) {
let branch = branches[i]
if (i > 0) {
if (!branch.isParams && !branch.isFullParams) {
output += ' else '
match(orgUrl) {
let url = orgUrl
if (url.length > 1 && url[url.length - 1] === '/') {
url = url.slice(0, -1)
}
let branch = this.root
let start = 1
let end = 1
let output
let name
let char
let params = {}
if (output = branch.children.get(url)) {
return {
handler: output.handler,
middlewares: output.middlewares,
params: params,
}
}
for (let i = 1; i <= url.length; i++) {
char = url[i]
if ((i === url.length || char === '/') && end > start) {
name = url.slice(start, end)
if (output = branch.children.get(name)) {
branch = output
}
else if (output = branch.children.get(__paramMapName)) {
branch = output
params[branch.paramName] = name
}
else if (output = branch.children.get(__fullParamMapName)) {
params[output.fullparamName] = url.slice(start)
return {
handler: output.handler,
middlewares: output.middlewares,
params: params,
}
} else {
// output += '} //'
output += '\n' + indentation
if (output = this.root.children.get(__fullParamMapName)) {
params = {
[output.fullparamName]: url.slice(1)
}
return {
handler: output.handler,
middlewares: output.middlewares,
params: params,
}
}
return null
}
i++
end = start = i
char = url[i]
}
// Check branch.handler. This can happen if route /::path is added
// and request is '/' it will attempt to match root which will fail
if (i >= url.length && branch.handler) {
return {
handler: branch.handler,
middlewares: branch.middlewares,
params: params,
}
}
if (!branch.isParams && !branch.isFullParams) {
output += `if (str.charCodeAt(${this.__getIndex(indent, 0, params)}) === ${branch.char.charCodeAt(0)}) { // ${branch.char}`
if (branch.path) {
output += '\n' + indentation + ` if (str.length === ${this.__getIndex(indent, 1, params)}) {`
output += this.__treeIntoCompiledCodeReturnPath(indentation + ' ', paths, branch, params)
output += '\n' + indentation + ` }`
}
if (char === '/') {
end = start = i + 1
} else {
addEndBracket = false
let paramVarName = (params.length + 1) + '_' + branch.paramVarName
output += `let s${paramVarName} = str.slice(${this.__getIndex(indent, 0, params)}${branch.isFullParams ? '' : `, str.indexOf('/', ${this.__getIndex(indent, 0, params)}) >>> 0`})`
output += '\n' + indentation + `let offset${paramVarName} = s${paramVarName}.length`
output += '\n' + indentation
params.push([branch.isParams || branch.isFullParams, paramVarName])
if (branch.isFullParams) {
output += this.__treeIntoCompiledCodeReturnPath(indentation, paths, branch, params)
} else if (branch.path) {
output += '\n' + indentation + `if (str.length === ${this.__getIndex(indent, 0, params)}) {`
output += this.__treeIntoCompiledCodeReturnPath(indentation + ' ', paths, branch, params)
output += '\n' + indentation + `}`
}
}
if (branch.children.length) {
if (branch.path) {
output += ' else '
} else {
output += '\n' + indentation + ' '
}
output += this.__treeIntoCompiledCodeBranch(paths, branch.children, indent + 1, params.slice())
}
if (addEndBracket) {
output += '\n' + indentation + '} '
end++
}
}
return output
}
__treeIntoCompiledCodeClosure(paths, tree, staticList) {
let output = 'return function RBufferStrSliceClosure(str) {'
if (staticList.size > 0) {
output += '\n let checkStatic = staticList.get(str)'
output += '\n if(checkStatic) {'
output += '\n return checkStatic'
output += '\n }'
if (output = this.root.children.get(__fullParamMapName)) {
params = {
[output.fullparamName]: url.slice(1)
}
return {
handler: output.handler,
middlewares: output.middlewares,
params: params,
}
}
if (tree.length) {
output += '\n ' + this.__treeIntoCompiledCodeBranch(paths, tree, 1, [])
}
output += '\n return null'
output += '\n}'
//console.log(output)
return new Function('paths', 'staticList', output)(paths, staticList)
}
compile() {
let splitPaths = this.__splitAndSortPaths(this.paths)
let tree = this.__buildTree(splitPaths.paramsPaths.slice())
this.match = this.__treeIntoCompiledCodeClosure(this.paths, tree, splitPaths.staticPaths)
}
match(url) {
this.compile()
return this.match(url)
return null
}
}
@ -794,7 +682,7 @@ export class Flaska {
for (let i = 0; i < this._nonces.length; i++) {
this._nonces[i] = crypto.randomBytes(16).toString('base64')
}
constructFunction += `
let nonce = this._nonces[this._noncesIndex] || crypto.randomBytes(16).toString('base64');
this._noncesIndex--;
@ -804,7 +692,7 @@ ctx.state.nonce = nonce;
constructFunction += 'ctx.headers = {'
constructFunction += `'Date': new Date().toUTCString(),`
for (let key of headerKeys) {
for (let key of headerKeys) {
if (key === 'Content-Security-Policy' && options.nonce.length) {
let groups = options.defaultHeaders[key].split(';')
for (let ni = 0; ni < options.nonce.length; ni++) {
@ -866,6 +754,12 @@ ctx.state.nonce = nonce;
this.patch = this.routers.PATCH.addRoute.bind(this.routers.PATCH)
}
_assertIsHandler(handler, name) {
if (typeof(handler) !== 'function') {
throw new Error(`${name} was called with a handler that was not a function`)
}
}
devMode() {
this._backuperror = this._onerror = function(err, ctx) {
ctx.log.error(err)
@ -901,7 +795,7 @@ ctx.state.nonce = nonce;
assertIsHandler(handler, 'onreqerror()')
this._onreqerror = handler
}
onreserror(handler) {
assertIsHandler(handler, 'onreserror()')
this._onreserror = handler
@ -916,7 +810,7 @@ ctx.state.nonce = nonce;
assertIsHandler(handler, 'beforeAsync()')
this._beforeAsync.push(handler)
}
after(handler) {
assertIsHandler(handler, 'after()')
this._after.push(handler)
@ -926,7 +820,7 @@ ctx.state.nonce = nonce;
assertIsHandler(handler, 'afterAsync()')
this._afterAsync.push(handler)
}
requestStart(req, res) {
let url = req.url
let search = ''
@ -1000,25 +894,37 @@ ctx.state.nonce = nonce;
ctx.params = route.params
let handlers = this.runHandlers(ctx, route.path.handlers, 0)
if (route.middlewares.length) {
let middle = this.handleMiddleware(ctx, route.middlewares, 0)
if (handlers && handlers.then) {
return handlers.then(() => {
if (middle && middle.then) {
return middle.then(() => {
return route.handler(ctx)
})
.then(() => {
this.requestEnd(null, ctx)
}, err => {
this.requestEnd(err, ctx)
})
}
}
let handler = route.handler(ctx)
if (handler && handler.then) {
return handler.then(() => {
this.requestEnd(null, ctx)
}, err => {
this.requestEnd(err, ctx)
})
}
this.requestEnd(null, ctx)
}
runHandlers(ctx, middles, index) {
handleMiddleware(ctx, middles, index) {
for (let i = index; i < middles.length; i++) {
let res = middles[i](ctx)
if (res && res.then) {
return res.then(() => {
return this.runHandlers(ctx, middles, i + 1)
return this.handleMiddleware(ctx, middles, i + 1)
})
}
}
@ -1046,7 +952,7 @@ ctx.state.nonce = nonce;
return
}
if (ctx.body == null && !handleUsed && ctx.status === 200) {
if (ctx.body === null && !handleUsed && ctx.status === 200) {
ctx.status = 204
}
@ -1083,7 +989,7 @@ ctx.state.nonce = nonce;
}
let length = 0
if (body instanceof Buffer) {
length = body.byteLength
ctx.type = ctx.type || 'application/octet-stream'
@ -1154,12 +1060,6 @@ ctx.state.nonce = nonce;
this[`_${type}AsyncCompiled`] = func.bind(this, ...this[`_${type}Async`])
}
}
this.routers.GET.compile()
this.routers.POST.compile()
this.routers.PUT.compile()
this.routers.DELETE.compile()
this.routers.OPTIONS.compile()
this.routers.PATCH.compile()
}
create() {
@ -1169,7 +1069,7 @@ ctx.state.nonce = nonce;
this.server.on('connection', function (socket) {
// Set socket idle timeout in milliseconds
socket.setTimeout(1000 * 60 * 5) // 5 minutes
// Wait for timeout event (socket will emit it when idle timeout elapses)
socket.on('timeout', function () {
// Call destroy again
@ -1190,7 +1090,7 @@ ctx.state.nonce = nonce;
}
this.create()
this.server.listen(port, ip, cb)
}
@ -1221,7 +1121,7 @@ ctx.state.nonce = nonce;
if (err) { return rej(err) }
// Waiting 0.1 second for it to close down
setTimeout(res, 100)
setTimeout(function() { res() }, 100)
})
})
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,22 +1,12 @@
import { Flaska } from './flaska.mjs'
const port = 51026
const port = 51024
const flaska = new Flaska({}, )
flaska.devMode()
flaska.get('/', function(ctx) {
ctx.body = { status: true }
})
flaska.get('/:item/asdf/herp/:derp/bla', function(ctx) {
ctx.body = { item: ctx.params.item }
})
flaska.get('/a', function(ctx) {
ctx.body = { status: true }
})
flaska.get('/error', function(ctx) {
process.exit(1)
})

View file

@ -5,11 +5,11 @@ import { createCtx, fakeHttp } from './helper.mjs'
const faker = fakeHttp()
t.describe('#constructor', function() {
t.test('should be able to override the http', function() {
t.test('should be able to override the http', function() {
let flaska = new Flaska({}, faker)
assert.strictEqual(flaska.http, faker)
})
t.test('it should have all the common verbs', function() {
let flaska = new Flaska({}, faker)
assert.ok(flaska.get)
@ -25,7 +25,7 @@ t.describe('#constructor', function() {
assert.ok(flaska.patch)
assert.strictEqual(typeof(flaska.patch), 'function')
})
t.test('the verbs GET and HEAD should be identical', function() {
let flaska = new Flaska({}, faker)
assert.ok(flaska.get)
@ -38,7 +38,7 @@ t.describe('#constructor', function() {
t.test('should have before default header generator', function() {
let flaska = new Flaska({}, faker)
assert.strictEqual(flaska._before.length, 1)
let ctx = {}
flaska._before[0](ctx)
@ -78,7 +78,7 @@ t.describe('#constructor', function() {
let ctx = {}
flaska._before[0](ctx)
let keys = Object.keys(defaultHeaders)
assert.strictEqual(Object.keys(ctx.headers).length, keys.length + 1)
@ -103,7 +103,7 @@ t.describe('#constructor', function() {
let ctx = {}
flaska._before[0](ctx)
assert.deepEqual(
Object.keys(ctx.headers).sort(),
['Server', 'Herp', 'X-Content-Type-Options','Content-Security-Policy','Cross-Origin-Opener-Policy','Cross-Origin-Resource-Policy','Cross-Origin-Embedder-Policy','Date'].sort()
@ -140,7 +140,7 @@ t.describe('#_nonce', function() {
set.add(entry)
})
assert.strictEqual(set.size, flaska._nonces.length)
let ctx = createCtx()
assert.notOk(ctx.state.nonce)
@ -148,12 +148,12 @@ t.describe('#_nonce', function() {
let oldIndex = flaska._noncesIndex
flaska._before[0](ctx)
assert.ok(ctx.state.nonce)
assert.strictEqual(flaska._noncesIndex, oldIndex - 1)
assert.strictEqual(flaska._nonces[oldIndex], ctx.state.nonce)
assert.strictEqual(ctx.headers['Server'], 'Flaska')
assert.strictEqual(ctx.headers['X-Content-Type-Options'], 'nosniff')
assert.strictEqual(ctx.headers['Content-Security-Policy'], `default-src 'self'; style-src 'self' 'unsafe-inline' 'nonce-${ctx.state.nonce}'; img-src * data: blob:; font-src 'self' data:; object-src 'none'; frame-ancestors 'none'; script-src 'nonce-${ctx.state.nonce}'`)
@ -162,7 +162,7 @@ t.describe('#_nonce', function() {
assert.strictEqual(ctx.headers['Cross-Origin-Embedder-Policy'], 'require-corp')
assert.ok(new Date(ctx.headers['Date']).getDate())
})
t.test('should always return nonce values even if it runs out in cache', function() {
let flaska = new Flaska({
nonce: ['script-src'],
@ -178,9 +178,9 @@ t.describe('#_nonce', function() {
}
assert.notOk(flaska._nonces[flaska._noncesIndex])
flaska._before[0](ctx)
assert.notOk(flaska._nonces[flaska._noncesIndex])
assert.ok(ctx.state.nonce)
@ -206,13 +206,13 @@ t.describe('#_nonce', function() {
set.add(entry)
})
assert.strictEqual(set.size, 5)
flaska._before[0](ctx)
flaska._before[0](ctx)
flaska._before[0](ctx)
assert.strictEqual(flaska._noncesIndex, 1)
flaska._after[0](ctx)
assert.strictEqual(flaska._noncesIndex, 2)
set.add(flaska._nonces[flaska._noncesIndex])
@ -227,7 +227,7 @@ t.describe('#_nonce', function() {
assert.strictEqual(flaska._noncesIndex, 4)
set.add(flaska._nonces[flaska._noncesIndex])
assert.strictEqual(set.size, 8)
flaska._after[0](ctx)
assert.strictEqual(flaska._noncesIndex, 4)
set.add(flaska._nonces[flaska._noncesIndex])
@ -249,12 +249,12 @@ t.describe('#_nonce', function() {
set.add(entry)
})
assert.strictEqual(set.size, 2)
flaska._before[0](ctx)
flaska._before[0](ctx)
assert.strictEqual(flaska._noncesIndex, -1)
flaska._before[0](ctx)
assert.strictEqual(flaska._noncesIndex, -2)
set.add(ctx.state.nonce)
@ -271,19 +271,19 @@ t.describe('#_nonce', function() {
assert.strictEqual(set.size, 5)
assert.strictEqual(Object.keys(flaska._nonces).length, 2)
flaska._after[0](ctx)
assert.strictEqual(flaska._noncesIndex, 0)
set.add(flaska._nonces[flaska._noncesIndex])
assert.strictEqual(set.size, 6)
assert.strictEqual(Object.keys(flaska._nonces).length, 2)
flaska._after[0](ctx)
assert.strictEqual(flaska._noncesIndex, 1)
set.add(flaska._nonces[flaska._noncesIndex])
assert.strictEqual(set.size, 7)
assert.strictEqual(Object.keys(flaska._nonces).length, 2)
flaska._after[0](ctx)
assert.strictEqual(flaska._noncesIndex, 1)
set.add(flaska._nonces[flaska._noncesIndex])
@ -299,7 +299,7 @@ t.describe('#log', function() {
assert.strictEqual(typeof(flaska.log.info), 'function')
assert.strictEqual(typeof(flaska.log.warn), 'function')
})
t.test('allow overwriting in options', function() {
const assertFunction = function() { return 1 }
let flaska = new Flaska({ log: {
@ -323,7 +323,7 @@ specialHandlers.forEach(function(type) {
let flaska = new Flaska({}, faker)
assert.strictEqual(typeof(flaska[type]), 'function')
})
t.test('validate handler', function() {
let flaska = new Flaska({}, faker)
assert.throws(function() { flaska[type]() }, /[Ff]unction/)
@ -424,7 +424,7 @@ t.describe('_onerror', function() {
const assertStatus = 431
const assertBody = { a: 1 }
const assertError = new HttpError(assertStatus, 'should not be seen', assertBody)
let ctx = createCtx()
let flaska = new Flaska({}, faker)
flaska._onerror(assertError, ctx)
@ -437,7 +437,7 @@ t.describe('_onerror', function() {
t.test('default valid handling of HttpError with no body', function() {
const assertStatus = 413
const assertError = new HttpError(assertStatus, 'should not be seen')
let ctx = createCtx()
let flaska = new Flaska({}, faker)
flaska._onerror(assertError, ctx)
@ -453,7 +453,7 @@ t.describe('_onerror', function() {
t.test('default valid handling of HttpError with missing status message', function() {
const assertStatus = 432
const assertError = new HttpError(assertStatus, 'should not be seen')
let ctx = createCtx()
let flaska = new Flaska({}, faker)
flaska._onerror(assertError, ctx)
@ -531,7 +531,7 @@ t.describe('#devMode()', function() {
const assertStatus = 431
const assertBody = { a: 1 }
const assertError = new HttpError(assertStatus, 'should not be seen', assertBody)
let ctx = createCtx()
let flaska = new Flaska({}, faker)
flaska.devMode()
@ -546,7 +546,7 @@ t.describe('#devMode()', function() {
const assertStatus = 413
const assertErrorMessage = 'A day'
const assertError = new HttpError(assertStatus, assertErrorMessage)
let ctx = createCtx()
let flaska = new Flaska({}, faker)
flaska.devMode()
@ -563,7 +563,7 @@ t.describe('#devMode()', function() {
const assertStatus = 432
const assertErrorMessage = 'Jet Coaster Ride'
const assertError = new HttpError(assertStatus, assertErrorMessage)
let ctx = createCtx()
let flaska = new Flaska({}, faker)
flaska.devMode()
@ -735,16 +735,16 @@ t.describe('#compile()', function() {
})
})
t.describe('#runHandlers()', function() {
t.describe('#handleMiddleware()', function() {
t.test('should work with empty array', function() {
let flaska = new Flaska({}, faker)
flaska.runHandlers({}, [], 0)
flaska.handleMiddleware({}, [], 0)
})
t.test('should work with correct index', function() {
let checkIsTrue = false
let flaska = new Flaska({}, faker)
flaska.runHandlers({}, [
flaska.handleMiddleware({}, [
function() { throw new Error('should not be thrown') },
function() { throw new Error('should not be thrown') },
function() { throw new Error('should not be thrown') },
@ -757,7 +757,7 @@ t.describe('#runHandlers()', function() {
const assertCtx = createCtx({ a: 1 })
let checkCounter = 0
let flaska = new Flaska({}, faker)
flaska.runHandlers(assertCtx, [
flaska.handleMiddleware(assertCtx, [
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
@ -771,7 +771,7 @@ t.describe('#runHandlers()', function() {
const assertCtx = createCtx({ a: 1 })
let checkCounter = 0
let flaska = new Flaska({}, faker)
let result = flaska.runHandlers(assertCtx, [
let result = flaska.handleMiddleware(assertCtx, [
function(ctx) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 0); checkCounter++ },
function(ctx) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 1); checkCounter++ },
function(ctx) { return new Promise(function(res) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 2); checkCounter++; res() }) },
@ -793,19 +793,19 @@ t.describe('#runHandlers()', function() {
const assertError = { a: 1 }
let checkCounter = 0
let flaska = new Flaska({}, faker)
let err = await assert.isRejected(flaska.runHandlers({}, [
let err = await assert.isRejected(flaska.handleMiddleware({}, [
function() { },
function() { return new Promise(function(res, rej) { rej(assertError) }) },
function() { throw new Error('should not be seen') },
], 0))
assert.strictEqual(err, assertError)
err = await assert.isRejected(flaska.runHandlers({}, [
err = await assert.isRejected(flaska.handleMiddleware({}, [
function() { },
function() { return Promise.reject(assertError) },
function() { throw new Error('should not be seen') },
], 0))
assert.strictEqual(err, assertError)
err = await assert.isRejected(flaska.runHandlers({}, [
err = await assert.isRejected(flaska.handleMiddleware({}, [
function() { },
function() { return Promise.resolve() },
function() { throw assertError },
@ -977,7 +977,7 @@ t.describe('#listenAsync()', function() {
assert.strictEqual(checkPort, assertPort)
assert.strictEqual(checkIp, '::')
})
t.test('call http and listenAsync correctly if supported', async function() {
const assertPort = 4632
const assertIp = 'asdf'
@ -1002,7 +1002,7 @@ t.describe('#listenAsync()', function() {
assert.strictEqual(stubListenAsync.firstCall[0], assertPort)
assert.strictEqual(stubListenAsync.firstCall[1], assertIp)
})
t.test('call http and listenAsync correctly if supported and ip is null', async function() {
const assertPort = 325897235
const assertReturns = { a: 1 }

View file

@ -30,7 +30,7 @@ t.describe('#requestStart()', function() {
assert.strictEqual(onResError.callCount, 1)
assert.strictEqual(onResError.firstCall[0], assertErrorTwo)
assert.strictEqual(onResError.firstCall[1], ctx)
assert.strictEqual(assertRes.on.secondCall[0], 'finish')
assert.strictEqual(typeof(assertRes.on.secondCall[1]), 'function')
assert.strictEqual(onEnded.callCount, 0)
@ -220,7 +220,7 @@ t.describe('#requestStart()', function() {
}), createRes())
})
t.test('calls handlers correctly', function(cb) {
t.test('calls handleMiddleware correctly', function(cb) {
const assertError = new Error('test')
const assertMiddles = [1, 2]
const assertParams = { a: 1, b: 2 }
@ -231,11 +231,12 @@ t.describe('#requestStart()', function() {
flaska.routers.GET.match = function() {
return {
path: { handlers: assertMiddles, },
handler: function() {},
middlewares: assertMiddles,
params: assertParams,
}
}
flaska.runHandlers = function(ctx, middles, index) {
flaska.handleMiddleware = function(ctx, middles, index) {
assert.strictEqual(index, 0)
assert.strictEqual(middles, assertMiddles)
checkMiddleCtx = ctx
@ -264,7 +265,7 @@ t.describe('#requestStart()', function() {
checkHandlerCtx = ctx
throw assertError
}
let flaska = new Flaska({}, faker)
flaska.get('/:id', handler)
flaska.compile()
@ -288,7 +289,7 @@ t.describe('#requestStart()', function() {
let handler = function() {
throw new Error('should not be called')
}
let flaska = new Flaska({}, faker)
flaska.on404(on404Error)
flaska.get('/test', function() { throw new Error('should not be called') })
@ -306,7 +307,7 @@ t.describe('#requestStart()', function() {
method: 'GET',
}), createRes())
})
t.test('calls 404 if route handler is not found and supports promise', function(cb) {
let checkCtx = null
const assertError = new Error('should be seen')
@ -314,7 +315,7 @@ t.describe('#requestStart()', function() {
checkCtx = ctx
return Promise.resolve().then(function() { return Promise.reject(assertError) })
}
let flaska = new Flaska({}, faker)
flaska.on404(on404Error)
flaska.get('/test', function() { throw new Error('should not be called') })
@ -335,7 +336,7 @@ t.describe('#requestStart()', function() {
t.test(`should handle unexpected errors in on404 correctly`, function(cb) {
const assertError = new Error('should be seen')
let checkCtx = null
let flaska = new Flaska({}, faker)
flaska.on404(function(ctx) {
checkCtx = ctx
@ -359,7 +360,7 @@ t.describe('#requestStart()', function() {
t.test(`should handle unexpected errors in middleware correctly`, function(cb) {
const assertError = new Error('should be seen')
let checkCtx = null
let flaska = new Flaska({}, faker)
let middles = [function(ctx) {
checkCtx = ctx
@ -386,10 +387,13 @@ t.describe('#requestStart()', function() {
assert.strictEqual(ctx.params.path, 'test/something/here')
ctx.body = assertBody
}
let flaska = new Flaska({}, faker)
flaska.get('/::path', handler)
flaska.compile()
flaska.handleMiddleware = function() {
throw new Error('should not be called')
}
flaska.requestEnd = cb.finish(function(err, ctx) {
assert.notOk(err)
@ -403,7 +407,7 @@ t.describe('#requestStart()', function() {
}), createRes())
})
t.test('calls runHandlers correctly if is promise', function(cb) {
t.test('calls handleMiddleware correctly if is promise', function(cb) {
const assertError = new Error('test')
const assertMiddles = [1]
@ -412,11 +416,11 @@ t.describe('#requestStart()', function() {
flaska.routers.GET.match = function() {
return {
path: { handlers: function() {}, },
handler: function() {},
middlewares: assertMiddles,
}
}
flaska.runHandlers = function() {
flaska.handleMiddleware = function() {
return Promise.resolve().then(function() { return Promise.reject(assertError) })
}
@ -440,7 +444,7 @@ t.describe('#requestStart()', function() {
checkHandlerCtx = ctx
throw assertError
}
let flaska = new Flaska({}, faker)
flaska.get('/:id', [function() { return Promise.resolve() }], handler)
flaska.compile()
@ -466,7 +470,7 @@ t.describe('#requestStart()', function() {
checkHandlerCtx = ctx
return Promise.resolve().then(function() { return Promise.reject(assertError) })
}
let flaska = new Flaska({}, faker)
flaska.get('/:id', [function() { return Promise.resolve() }], handler)
flaska.compile()
@ -500,7 +504,7 @@ t.describe('#requestStart()', function() {
ctx.body = assertBody
})
}
let flaska = new Flaska({}, faker)
flaska.get('/::path', [middle], handler)
flaska.compile()
@ -517,7 +521,7 @@ t.describe('#requestStart()', function() {
method: 'GET',
}), createRes())
})
t.test('calls route handler correctly if promise', function(cb) {
const assertError = new Error('test')
let checkHandlerCtx = null
@ -528,7 +532,7 @@ t.describe('#requestStart()', function() {
return Promise.reject(assertError)
})
}
let flaska = new Flaska({}, faker)
flaska.get('/:id', handler)
flaska.compile()
@ -554,11 +558,15 @@ t.describe('#requestStart()', function() {
ctx.body = assertBody
})
}
let flaska = new Flaska({}, faker)
flaska.get('/::path', [], handler)
flaska.compile()
flaska.handleMiddleware = function() {
throw new Error('should not be called')
}
flaska.requestEnd = cb.finish(function(err, ctx) {
assert.notOk(err)
assert.ok(ctx)

View file

@ -7,7 +7,7 @@ import { setTimeout } from 'timers/promises'
import { Flaska, FormidableHandler, FileResponse } from '../flaska.mjs'
import Client from './client.mjs'
const port = 51025
const port = 51024
const log = {
fatal: stub(),
error: stub(),
@ -111,7 +111,7 @@ t.describe('/file', function() {
assert.ok(target.closed)
assert.ok(file.closed)
let [statSource, statTarget] = await Promise.all([
fs.stat('./test/test.png'),
fs.stat('./test_tmp.png'),
@ -136,11 +136,11 @@ t.describe('/file', function() {
assert.notOk(file.closed)
req.destroy()
while (!file.closed) {
await setTimeout(10)
}
assert.strictEqual(log.error.callCount, 0)
assert.strictEqual(log.info.callCount, 0)
@ -195,7 +195,7 @@ t.describe('/filehandle', function() {
assert.strictEqual(res.data, 'llo ')
assert.strictEqual(res.headers['content-length'], '4')
res = await client.customRequest('GET', '/filehandle', null, { getRaw: true, agent: agent,
headers: {
'Range': 'bytes=0-0'
@ -311,15 +311,15 @@ t.describe('HEAD', function() {
t.describe('/file', function() {
t.test('server return HEAD for pipes', async function() {
log.error.reset()
let res = await client.customRequest('HEAD', '/file', null, { getRaw: true, agent: agent })
while (!file.closed) {
await setTimeout(10)
}
assert.ok(file.closed)
let statSource = await fs.stat('./test/test.png')
assert.strictEqual(res.data, '')
@ -328,37 +328,37 @@ t.describe('HEAD', function() {
})
t.test('server should autoclose body file handles on errors', async function() {
reset()
file = null
let req = await client.customRequest('HEAD', '/file/leak', null, { returnRequest: true })
req.end()
while (!file) {
await setTimeout(10)
}
assert.ok(file)
assert.notOk(file.closed)
req.destroy()
while (!file.closed) {
await setTimeout(10)
}
assert.strictEqual(log.error.callCount, 0)
assert.strictEqual(log.info.callCount, 0)
assert.ok(file.closed)
})
})
t.describe('/filehandle', function() {
t.test('server should send correctly', async function() {
log.error.reset()
let res = await client.customRequest('HEAD', '/filehandle', null, { getRaw: true, agent: agent })
assert.strictEqual(res.status, 200)
assert.strictEqual(res.headers['content-length'], '11')
@ -369,7 +369,7 @@ t.describe('HEAD', function() {
let etag = res.headers['etag']
assert.ok(d.getTime())
assert.strictEqual(res.data, '')
res = await client.customRequest('HEAD', '/filehandle', null, { getRaw: true, agent: agent,
headers: {
'If-Modified-Since': d.toUTCString()
@ -378,7 +378,7 @@ t.describe('HEAD', function() {
assert.strictEqual(res.status, 304)
assert.strictEqual(res.data, '')
assert.strictEqual(res.headers['etag'], etag)
res = await client.customRequest('HEAD', '/filehandle', null, { getRaw: true, agent: agent,
headers: {
'If-None-Match': etag
@ -387,7 +387,7 @@ t.describe('HEAD', function() {
assert.strictEqual(res.status, 304)
assert.strictEqual(res.data, '')
assert.strictEqual(res.headers['etag'], etag)
res = await client.customRequest('HEAD', '/filehandle', null, { getRaw: true, agent: agent,
headers: {
'Range': 'bytes=2-5'
@ -396,8 +396,8 @@ t.describe('HEAD', function() {
assert.strictEqual(res.status, 206)
assert.strictEqual(res.data, '')
assert.strictEqual(res.headers['content-length'], '4')
res = await client.customRequest('HEAD', '/filehandle', null, { getRaw: true, agent: agent,
headers: {
'Range': 'bytes=0-0'
@ -407,7 +407,7 @@ t.describe('HEAD', function() {
assert.strictEqual(res.data, '')
assert.strictEqual(res.headers['content-length'], '1')
})
t.after(function() {
agent.destroy()
})

View file

@ -9,7 +9,7 @@ t.describe('#addRoute()', function() {
assert.throws(function() { router.addRoute(':test') }, /forward slash/)
assert.throws(function() { router.addRoute('test/test2') }, /forward slash/)
})
t.test('fail if missing handler', function() {
let router = new FlaskaRouter()
assert.throws(function() { router.addRoute('/') }, /handler/)
@ -45,21 +45,8 @@ t.describe('#addRoute()', function() {
router.addRoute('/:test/bla', function() {})
router.addRoute('/bla/bla', function() {})
router.addRoute('/bla/bla/bla', function() {})
assert.throws(function() { router.addRoute('/:asdf', function() {}) }, /existing/)
assert.throws(function() { router.addRoute('/:test/asdf/:foobar', function() {}) }, /existing/)
})
t.test('fail if adding anything after a fullparam', function() {
let router = new FlaskaRouter()
assert.throws(function() { router.addRoute('/::bla/:bla', function() {}) }, /after/)
assert.throws(function() { router.addRoute('/:test/::bla/test', function() {}) }, /after/)
assert.throws(function() { router.addRoute('/::test/bla', function() {}) }, /after/)
})
t.test('should work with param and full param side by side', function() {
let router = new FlaskaRouter()
router.addRoute('/:bla', function() {})
router.addRoute('/::bla', function() {})
assert.throws(function() { router.addRoute('/:asdf/', function() {}) }, /param/)
assert.throws(function() { router.addRoute('/:test/asdf/:foobar', function() {}) }, /param/)
})
t.test('fail if adding multiple fullparam', function() {
@ -69,8 +56,10 @@ t.describe('#addRoute()', function() {
router.addRoute('/:test/bla', function() {})
router.addRoute('/:test/::bla', function() {})
router.addRoute('/bla/bla/bla', function() {})
assert.throws(function() { router.addRoute('/:test/asdf/::bla', function() {}) }, /existing/)
assert.throws(function() { router.addRoute('/:test/::bla', function() {}) }, /existing/)
assert.throws(function() { router.addRoute('/:test/asdf/::bla/fail', function() {}) }, /full.+param/)
assert.throws(function() { router.addRoute('/:test/::bla/test', function() {}) }, /full.+param/)
assert.throws(function() { router.addRoute('/:test/:bla', function() {}) }, /full.+param/)
assert.throws(function() { router.addRoute('/::test', function() {}) }, /partial.+param/)
})
t.test('add route correctly', function() {
@ -78,7 +67,39 @@ t.describe('#addRoute()', function() {
let router = new FlaskaRouter()
router.addRoute('/a/b/c', assertHandler)
let result = router.match('/a/b/c')
assert.strictEqual(result.path.handlers[0], assertHandler)
assert.strictEqual(result.handler, assertHandler)
})
t.test('add param route correctly', function() {
let assertHandler = function() { return 1 }
let router = new FlaskaRouter()
router.addRoute('/a/:b/c', assertHandler)
assert.ok(router.root.children.get('a'))
assert.ok(router.root.children.get('a').children.get('__param'))
assert.strictEqual(router.root.children.get('a').children.get('__param').paramName, 'b')
assert.ok(router.root.children.get('a').children.get('__param').children.get('c'))
assert.strictEqual(router.root.children.get('a').children.get('__param').children.get('c').handler, assertHandler)
})
t.test('add full param route correctly', function() {
let assertHandler = function() { return 1 }
let router = new FlaskaRouter()
router.addRoute('/a/::b', assertHandler)
assert.ok(router.root.children.get('a'))
assert.ok(router.root.children.get('a').children.get('__fullparam'))
assert.strictEqual(router.root.children.get('a').children.get('__fullparam').fullparamName, 'b')
assert.strictEqual(router.root.children.get('a').children.get('__fullparam').handler, assertHandler)
})
t.test('add param route correctly', function() {
let assertHandler = function() { return 1 }
let router = new FlaskaRouter()
router.addRoute('/a/:b/c', assertHandler)
assert.ok(router.root.children.get('a'))
assert.ok(router.root.children.get('a').children.get('__param'))
assert.strictEqual(router.root.children.get('a').children.get('__param').paramName, 'b')
assert.ok(router.root.children.get('a').children.get('__param').children.get('c'))
assert.strictEqual(router.root.children.get('a').children.get('__param').children.get('c').handler, assertHandler)
})
t.test('support single middlewares correctly', function() {
@ -86,10 +107,10 @@ t.describe('#addRoute()', function() {
let assertMiddleware = function() { return 1 }
let router = new FlaskaRouter()
router.addRoute('/a', assertMiddleware, assertHandler)
let result = router.match('/a')
assert.strictEqual(result.path.handlers.length, 2)
assert.strictEqual(result.path.handlers[0], assertMiddleware)
assert.strictEqual(result.path.handlers[1], assertHandler)
assert.ok(router.root.children.get('a'))
assert.strictEqual(router.root.children.get('a').handler, assertHandler)
assert.strictEqual(router.root.children.get('a').middlewares.length, 1)
assert.strictEqual(router.root.children.get('a').middlewares[0], assertMiddleware)
})
t.test('support multi middlewares correctly', function() {
@ -98,15 +119,15 @@ t.describe('#addRoute()', function() {
let router = new FlaskaRouter()
router.addRoute('/a', [assertMiddleware], assertHandler)
router.addRoute('/b', [assertMiddleware, assertMiddleware], assertHandler)
let resultA = router.match('/a')
assert.strictEqual(resultA.path.handlers.length, 2)
assert.strictEqual(resultA.path.handlers[0], assertMiddleware)
assert.strictEqual(resultA.path.handlers[1], assertHandler)
let resultB = router.match('/b')
assert.strictEqual(resultB.path.handlers.length, 3)
assert.strictEqual(resultB.path.handlers[0], assertMiddleware)
assert.strictEqual(resultB.path.handlers[1], assertMiddleware)
assert.strictEqual(resultB.path.handlers[2], assertHandler)
assert.ok(router.root.children.get('a'))
assert.strictEqual(router.root.children.get('a').handler, assertHandler)
assert.strictEqual(router.root.children.get('a').middlewares.length, 1)
assert.strictEqual(router.root.children.get('a').middlewares[0], assertMiddleware)
assert.ok(router.root.children.get('b'))
assert.strictEqual(router.root.children.get('b').handler, assertHandler)
assert.strictEqual(router.root.children.get('b').middlewares.length, 2)
assert.strictEqual(router.root.children.get('b').middlewares[0], assertMiddleware)
assert.strictEqual(router.root.children.get('b').middlewares[1], assertMiddleware)
})
})
@ -115,47 +136,94 @@ t.describe('#match()', function() {
let assertMatched = false
let router = new FlaskaRouter()
router.addRoute('/test', function() { assertMatched = true })
let result = router.match('/test').path
assert.strictEqual(result.handlers.length, 1)
result.handlers[0]()
let result = router.match('/test')
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
result.handler()
assert.strictEqual(assertMatched, true)
// Test with extra slash at the end
assertMatched = false
result = router.match('/test/')
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
result.handler()
assert.strictEqual(assertMatched, true)
})
t.test('return middlewares in handlers', function() {
t.test('return middlewares', function() {
let assertMatched = false
let assertMiddleware = function() { assertMatched = true }
let router = new FlaskaRouter()
router.addRoute('/test', assertMiddleware, function() { })
let result = router.match('/test').path
assert.strictEqual(result.handlers.length, 2)
result.handlers[0]()
let result = router.match('/test')
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 1)
result.middlewares[0]()
assert.strictEqual(assertMatched, true)
// Test with extra slash at the end
assertMatched = false
result = router.match('/test/')
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 1)
result.middlewares[0]()
assert.strictEqual(assertMatched, true)
})
t.test('match variable paths', function() {
const assertParameter = 'bla'
let assertMatched = false
let router = new FlaskaRouter()
router.addRoute('/test/:id', function() { assertMatched = true })
let result = router.match('/test/' + assertParameter)
assert.strictEqual(result.path.handlers.length, 1)
result.path.handlers[0]()
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
result.handler()
assert.strictEqual(assertMatched, true)
assert.strictEqual(result.params.id, assertParameter)
// Test with extra slash at the end
assertMatched = false
result = router.match('/test/' + assertParameter + '/')
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
result.handler()
assert.strictEqual(assertMatched, true)
assert.strictEqual(result.params.id, assertParameter)
})
t.test('match full path variable paths', function() {
const assertParameter = 'bla/bla/bla'
let assertMatched = false
let router = new FlaskaRouter()
router.addRoute('/test/::id', function() { assertMatched = true })
let result = router.match('/test/' + assertParameter)
assert.strictEqual(result.path.handlers.length, 1)
result.path.handlers[0]()
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
result.handler()
assert.strictEqual(assertMatched, true)
assert.strictEqual(result.params.id, assertParameter)
// Test with extra slash at the end
assertMatched = false
result = router.match('/test/' + assertParameter + '/')
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
result.handler()
assert.strictEqual(assertMatched, true)
assert.strictEqual(result.params.id, assertParameter)
})
t.test('match full path root path properly', function() {
const assertParamFunc = function() { }
const assertFullFunc = function() { }
@ -163,13 +231,15 @@ t.describe('#match()', function() {
router.addRoute('/test/:bla', assertParamFunc)
router.addRoute('/::id', assertFullFunc)
let result = router.match('/test/123')
assert.strictEqual(result.path.handlers.length, 1)
assert.strictEqual(result.path.handlers[0], assertParamFunc)
assert.strictEqual(result.handler, assertParamFunc)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
assert.strictEqual(result.params.bla, '123')
result = router.match('/test/123/asdf')
assert.strictEqual(result.path.handlers.length, 1)
assert.strictEqual(result.path.handlers[0], assertFullFunc)
assert.strictEqual(result.handler, assertFullFunc)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
assert.strictEqual(result.params.id, 'test/123/asdf')
assert.notOk(result.params.bla)
})
@ -180,8 +250,19 @@ t.describe('#match()', function() {
router.addRoute('/test/:id', function() { assertMatched = false })
router.addRoute('/test/:id/test1', function() { })
let result = router.match('/test/asdf/test1')
assert.strictEqual(result.path.handlers.length, 1)
result.path.handlers[0]()
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
result.handler()
assert.strictEqual(assertMatched, true)
assert.strictEqual(result.params.id, 'asdf')
// Test with extra slash at the end
result = router.match('/test/asdf/test1/')
assert.ok(result.handler)
assert.ok(result.middlewares)
assert.strictEqual(result.middlewares.length, 0)
result.handler()
assert.strictEqual(assertMatched, true)
assert.strictEqual(result.params.id, 'asdf')
})
@ -196,25 +277,24 @@ t.describe('#match()', function() {
router.addRoute('/foo/::path', assertFunction)
router.addRoute('/::path', assertFailFunction)
assert.strictEqual(router.match('/test/123').path.handlers[0], assertFunction)
assert.strictEqual(router.match('/test/asdfasdg').path.handlers[0], assertFunction)
assert.strictEqual(router.match('/test/test/sdafsda').path.handlers[0], assertFunction)
assert.strictEqual(router.match('/test/test/sdafsda/gdfshe4/43y34/wtaw').path.handlers[0], assertFunction)
assert.strictEqual(router.match('/foo/123').path.handlers[0], assertFunction)
assert.strictEqual(router.match('/foo/bar/baz/test').path.handlers[0], assertFunction)
assert.strictEqual(router.match('/test/123').handler, assertFunction)
assert.strictEqual(router.match('/test/asdfasdg').handler, assertFunction)
assert.strictEqual(router.match('/test/test/sdafsda').handler, assertFunction)
assert.strictEqual(router.match('/test/test/sdafsda/gdfshe4/43y34/wtaw').handler, assertFunction)
assert.strictEqual(router.match('/foo/123').handler, assertFunction)
assert.strictEqual(router.match('/foo/bar/baz/test').handler, assertFunction)
assert.ok(router.match('/test/123/yweherher/reher/h34h34/'))
assert.strictEqual(router.match('/test/123/yweherher/reher/h34h34/').path.handlers[0], assertFailFunction)
assert.strictEqual(router.match('/test/123/yweherher/reher/h34h34/').handler, assertFailFunction)
assert.ok(router.match('/test/foo/bar'))
assert.strictEqual(router.match('/test/foo/bar').path.handlers[0], assertFailFunction)
assert.strictEqual(router.match('/test/foo/bar').handler, assertFailFunction)
assert.ok(router.match('/'))
assert.strictEqual(router.match('/').path.handlers[0], assertFailFunction)
assert.strictEqual(router.match('/').handler, assertFailFunction)
assert.ok(router.match('/something/else/goes/here'))
assert.strictEqual(router.match('/something/else/goes/here').path.handlers[0], assertFailFunction)
assert.strictEqual(router.match('/something/else/goes/here').handler, assertFailFunction)
router.addRoute('/', assertRootFunction)
router.compile()
assert.ok(router.match('/'))
assert.strictEqual(router.match('/').path.handlers[0], assertRootFunction)
assert.strictEqual(router.match('/').handler, assertRootFunction)
})
t.test('return null when no match is found', function() {

View file

@ -1,20 +0,0 @@
import { Flaska } from './flaska_fast.mjs'
const port = 51028
const flaska = new Flaska({}, )
flaska.get('/', function(ctx) {
ctx.body = { status: true }
})
flaska.get('/:item/asdf/herp/:derp/bla', function(ctx) {
ctx.body = { item: ctx.params.item }
})
flaska.get('/error', function(ctx) {
process.exit(1)
})
flaska.listen(port, function() {
console.log('listening on port', port)
})

View file

@ -1,20 +0,0 @@
import { Flaska } from './flaska_buffer.mjs'
const port = 51027
const flaska = new Flaska({}, )
flaska.get('/', function(ctx) {
ctx.body = { status: true }
})
flaska.get('/:item/asdf/herp/:derp/bla', function(ctx) {
ctx.body = { item: ctx.params.item }
})
flaska.get('/error', function(ctx) {
process.exit(1)
})
flaska.listen(port, function() {
console.log('listening on port', port)
})