Compare commits
25 commits
Author | SHA1 | Date | |
---|---|---|---|
d031efb89f | |||
bed505fbe2 | |||
479f98a367 | |||
ee0326c9f4 | |||
566c59ec95 | |||
f48d63038d | |||
2e1dadbdea | |||
8db66f416a | |||
d5459cbcb9 | |||
01a916eb2d | |||
598548d97b | |||
8a49e38285 | |||
6d4d62e79c | |||
7401b3bd2c | |||
95e6c2dcac | |||
74771d92cf | |||
2b69013c04 | |||
8a56969015 | |||
5f916e97ea | |||
baf2d896c1 | |||
e9c600b869 | |||
568c620782 | |||
0c22fe9577 | |||
3a0064c563 | |||
e7909cc84b |
40 changed files with 3310 additions and 2401 deletions
44
.forgejo/workflows/deploy.yml
Normal file
44
.forgejo/workflows/deploy.yml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: arch
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Install dependencies
|
||||||
|
run: time pnpm install
|
||||||
|
- name: Run Tests
|
||||||
|
run: pnpm run test --ignore-only
|
||||||
|
- name: Deply if new version
|
||||||
|
run: |
|
||||||
|
echo ""
|
||||||
|
echo "------------------------------------"
|
||||||
|
echo ""
|
||||||
|
CURR_VER="$(cat package.json | jq -r .name)_v$(cat package.json | jq -r .version)"
|
||||||
|
CURR_NAME="$(cat package.json | jq -r .name) v$(cat package.json | jq -r .version)"
|
||||||
|
|
||||||
|
echo "Checking https://git.nfp.is/api/v1/repos/${{ github.repository }}/releases for name ${CURR_NAME}"
|
||||||
|
|
||||||
|
if curl -s -X GET -H "Authorization: token ${{ secrets.deploytoken }}" https://git.nfp.is/api/v1/repos/${{ github.repository }}/releases | grep -o "\"name\":\"${CURR_NAME}\"" > /dev/null; then
|
||||||
|
echo "Skipping ${{ github.job }} since $CURR_NAME already exists";
|
||||||
|
exit;
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "New release ${CURR_VER} found, beginning deployment"
|
||||||
|
|
||||||
|
echo "Creating ${CURR_VER} release on forgejo"
|
||||||
|
curl \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: token ${{ secrets.deploytoken }}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
https://git.nfp.is/api/v1/repos/${{ github.repository }}/releases \
|
||||||
|
-d "{\"tag_name\":\"${CURR_VER}\",\"name\":\"${CURR_NAME}\",\"body\":\"Automatic release from Appveyor from ${{ github.sha }} :\n\n${{ github.event.head_commit.message }}\"}" | jq
|
||||||
|
|
||||||
|
echo "//registry.npmjs.org/:_authToken=${{ secrets.npmtoken }}" > ~/.npmrc
|
||||||
|
echo "Publishing new version to npm"
|
||||||
|
npm publish
|
24
README.md
24
README.md
|
@ -126,9 +126,9 @@ flaska.get('/api/test', function(ctx) {
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
### File stream/pipe
|
### pipe
|
||||||
|
|
||||||
In cases where the response body is a pipe object (detected from the existance of `.pipe` property), flaska will automatically pipe the file for you. In addition, if a file stream is used, it will read the extension of the file being streamed and automatically fill in the mime-type for you in the `Content-Type` header.
|
In cases where the response body is a pipe object (detected from the existance of `.pipe` property), flaska will automatically pipe it for you. In addition, if a file stream is used, it will read the extension of the file being streamed and automatically fill in the mime-type for you in the `Content-Type` header.
|
||||||
|
|
||||||
```
|
```
|
||||||
flaska.get('/test.png', function(ctx) {
|
flaska.get('/test.png', function(ctx) {
|
||||||
|
@ -138,6 +138,22 @@ flaska.get('/test.png', function(ctx) {
|
||||||
|
|
||||||
Flaska will automatically close the file stream for you so you don't have to worry about that.
|
Flaska will automatically close the file stream for you so you don't have to worry about that.
|
||||||
|
|
||||||
|
### FileResponse
|
||||||
|
|
||||||
|
Alternatively, if you want proper file support, I recommend using FileResponse object:
|
||||||
|
|
||||||
|
```
|
||||||
|
import { FileResponse } from '../flaska.mjs'
|
||||||
|
|
||||||
|
flaska.get('/test.txt', function(ctx) {
|
||||||
|
return fs.stat('./test/test.txt').then(function(stat) {
|
||||||
|
ctx.body = new FileResponse('./test/test.txt', stat)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
This performs a real file stream support, uses pipes and supports all the HTTP shenanigans when it comes to dealing with files, including sending proper etag header, supporting partial response and lots of other things. This is one of the few libraries that actually implements full HTTP partial and etag support in a proper way, almost all other have one or two quirks that don't follow the spec properly.
|
||||||
|
|
||||||
### String
|
### String
|
||||||
|
|
||||||
In other instances, Flaska will `.toString()` the body and send it in response with the specified type or default to `text/plain` if unspecified.
|
In other instances, Flaska will `.toString()` the body and send it in response with the specified type or default to `text/plain` if unspecified.
|
||||||
|
@ -173,6 +189,10 @@ Parse the search query and create a map with key->value in `ctx.query`.
|
||||||
|
|
||||||
Parse incoming request body as json and store it in `ctx.req.body`.
|
Parse incoming request body as json and store it in `ctx.req.body`.
|
||||||
|
|
||||||
|
* `CorsHandler()`
|
||||||
|
|
||||||
|
A secure implementation for handling CORS requests.
|
||||||
|
|
||||||
* `FormidableHandler()`
|
* `FormidableHandler()`
|
||||||
|
|
||||||
Provides a wrapper to handle an incoming file upload using `Formidable@1`.
|
Provides a wrapper to handle an incoming file upload using `Formidable@1`.
|
||||||
|
|
116
benchmark/compiler/compiler.mjs
Normal file
116
benchmark/compiler/compiler.mjs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
import { printTree } from "./utils.mjs"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Child {
|
||||||
|
* char: 'a',
|
||||||
|
* children: [Child, Child],
|
||||||
|
* path: null / 'full/path',
|
||||||
|
* count: 0
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
function Child(char) {
|
||||||
|
this.char = char
|
||||||
|
this.children = []
|
||||||
|
this.path = null
|
||||||
|
this.count = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildChild(i, arr) {
|
||||||
|
let letter = new Child(arr[0][i])
|
||||||
|
let consume = []
|
||||||
|
if (arr[0].length === i + 1) {
|
||||||
|
letter.path = arr[0]
|
||||||
|
letter.count += 1
|
||||||
|
} else {
|
||||||
|
consume = [arr[0]]
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let y = 1; y < arr.length; y++) {
|
||||||
|
if (arr[y][i] !== letter.char) break
|
||||||
|
consume.push(arr[y])
|
||||||
|
}
|
||||||
|
|
||||||
|
letter.count += consume.length
|
||||||
|
while (consume.length) {
|
||||||
|
letter.children.push(buildChild(i + 1, consume))
|
||||||
|
consume.splice(0, letter.children[letter.children.length - 1].count)
|
||||||
|
}
|
||||||
|
return letter
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildTree(all) {
|
||||||
|
let paths = Array.from(new Set(all)).sort()
|
||||||
|
let builder = []
|
||||||
|
while (paths.length) {
|
||||||
|
builder.push(buildChild(0, paths))
|
||||||
|
paths.splice(0, builder[builder.length - 1].count)
|
||||||
|
}
|
||||||
|
printTree(builder)
|
||||||
|
return builder
|
||||||
|
}
|
||||||
|
|
||||||
|
function IfTreeBranch(branches, indent = 0) {
|
||||||
|
let output = ''
|
||||||
|
let indentation = ''.padStart(indent * 2)
|
||||||
|
|
||||||
|
for (let i = 0; i < branches.length; i++) {
|
||||||
|
let branch = branches[i]
|
||||||
|
output += `${i > 0 ? 'else ': ''}if (str.charCodeAt(${indent}) === ${branch.char.charCodeAt(0)}) { // ${branch.char}`
|
||||||
|
|
||||||
|
if (branch.path) {
|
||||||
|
output += '\n' + indentation + ` if (str.length === ${branch.path.length}) {`
|
||||||
|
output += '\n' + indentation + ` return "${branch.path}"`
|
||||||
|
output += '\n' + indentation + ` }`
|
||||||
|
}
|
||||||
|
if (branch.children.length) {
|
||||||
|
if (branch.path) {
|
||||||
|
output += ' else '
|
||||||
|
} else {
|
||||||
|
output += '\n' + indentation + ' '
|
||||||
|
}
|
||||||
|
output += IfTreeBranch(branch.children, indent + 1)
|
||||||
|
}
|
||||||
|
output += '\n' + indentation + '} '
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compileTreeIntoIfs(tree) {
|
||||||
|
let output = IfTreeBranch(tree)
|
||||||
|
output += '\nreturn null'
|
||||||
|
return new Function('str', output)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function IfTreeBranchBuffer(branches, indent = 0) {
|
||||||
|
let output = ''
|
||||||
|
let indentation = ''.padStart(indent * 2)
|
||||||
|
|
||||||
|
for (let i = 0; i < branches.length; i++) {
|
||||||
|
let branch = branches[i]
|
||||||
|
output += `${i > 0 ? 'else ': ''}if (uint[${indent}] === ${branch.char.charCodeAt(0)}) { // ${branch.char}`
|
||||||
|
|
||||||
|
if (branch.path) {
|
||||||
|
output += '\n' + indentation + ` if (uint.length === ${branch.path.length}) {`
|
||||||
|
output += '\n' + indentation + ` return "${branch.path}"`
|
||||||
|
output += '\n' + indentation + ` }`
|
||||||
|
}
|
||||||
|
if (branch.children.length) {
|
||||||
|
if (branch.path) {
|
||||||
|
output += ' else '
|
||||||
|
} else {
|
||||||
|
output += '\n' + indentation + ' '
|
||||||
|
}
|
||||||
|
output += IfTreeBranchBuffer(branch.children, indent + 1)
|
||||||
|
}
|
||||||
|
output += '\n' + indentation + '} '
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compileTreeIntoIfsWithBuffer(tree) {
|
||||||
|
let output = `var uint = Buffer.from(str)\n`
|
||||||
|
output += IfTreeBranchBuffer(tree)
|
||||||
|
output += '\nreturn null'
|
||||||
|
return new Function('str', output)
|
||||||
|
}
|
39
benchmark/compiler/utils.mjs
Normal file
39
benchmark/compiler/utils.mjs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
export function printTree(children, indent = 0) {
|
||||||
|
if (!children.length) return
|
||||||
|
|
||||||
|
for (let child of children) {
|
||||||
|
console.log(child.char.padStart(1 + indent * 2))
|
||||||
|
printTree(child.children, indent + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function printIfNotEightyOne(fn) {
|
||||||
|
let opt = %GetOptimizationStatus(fn)
|
||||||
|
if (opt === 81) return
|
||||||
|
printCurrentStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function printCurrentStatus(fn) {
|
||||||
|
let opt = %GetOptimizationStatus(fn)
|
||||||
|
console.log(`${opt.toString(2).padStart(17, '0').split('').join(' ')} (${opt})`)
|
||||||
|
}
|
||||||
|
export function printStatusHelperText() {
|
||||||
|
console.log(`┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─╸ is function
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───╸ is never optimized
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ │ │ └─────╸ is always optimized
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ │ └───────╸ is maybe deoptimized
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ └─────────╸ is optimized
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ └───────────╸ is optimized by TurboFan
|
||||||
|
│ │ │ │ │ │ │ │ │ │ └─────────────╸ is interpreted
|
||||||
|
│ │ │ │ │ │ │ │ │ └───────────────╸ is marked for optimization
|
||||||
|
│ │ │ │ │ │ │ │ └─────────────────╸ is marked for concurrent optimization
|
||||||
|
│ │ │ │ │ │ │ └───────────────────╸ is optimizing concurrently
|
||||||
|
│ │ │ │ │ │ └─────────────────────╸ is executing
|
||||||
|
│ │ │ │ │ └───────────────────────╸ topmost frame is turbo fanned
|
||||||
|
│ │ │ │ └─────────────────────────╸ lite mode
|
||||||
|
│ │ │ └───────────────────────────╸ marked for deoptimization
|
||||||
|
│ │ └─────────────────────────────╸ baseline
|
||||||
|
│ └───────────────────────────────╸ topmost frame is interpreted
|
||||||
|
└─────────────────────────────────╸ topmost frame is baseline`)
|
||||||
|
}
|
|
@ -6,6 +6,15 @@ export const overrideDummy = function(override) {
|
||||||
|
|
||||||
export const dummy = function() { callItem() }
|
export const dummy = function() { callItem() }
|
||||||
|
|
||||||
|
export const testRoutes = [
|
||||||
|
// '/api/articles/:id/file/:fileId',
|
||||||
|
'/',
|
||||||
|
// '/api/articles',
|
||||||
|
// '/api/articles/:id/file',
|
||||||
|
// '/api/articles/:id',
|
||||||
|
'/::rest',
|
||||||
|
]
|
||||||
|
|
||||||
export const allRoutes = [
|
export const allRoutes = [
|
||||||
'/',
|
'/',
|
||||||
'/api/articles',
|
'/api/articles',
|
||||||
|
@ -23,6 +32,22 @@ export const allRoutes = [
|
||||||
'/api/pages/:id/articles/public',
|
'/api/pages/:id/articles/public',
|
||||||
'/api/staff',
|
'/api/staff',
|
||||||
'/api/staff/:id',
|
'/api/staff/:id',
|
||||||
|
'/::rest',
|
||||||
|
]
|
||||||
|
|
||||||
|
export const allRoutesGoldenBalance = [
|
||||||
|
'/',
|
||||||
|
'/a/:a/aaaaaaaaaaaaaaaaaaaaaaaaaaaaa/:aa',
|
||||||
|
'/b/:b/bbbbbbbbbbbbbbbbbbbbbbbbbbbbb/:bb',
|
||||||
|
'/c/:c/ccccccccccccccccccccccccccccc/:cc',
|
||||||
|
'/d/:d/ddddddddddddddddddddddddddddd/:dd',
|
||||||
|
'/e/:e/eeeeeeeeeeeeeeeeeeeeeeeeeeeee/:ee',
|
||||||
|
'/f/:f/fffffffffffffffffffffffffffff/:ff',
|
||||||
|
'/g/:g/ggggggggggggggggggggggggggggg/:gg',
|
||||||
|
'/h/:h/hhhhhhhhhhhhhhhhhhhhhhhhhhhhh/:hh',
|
||||||
|
'/i/:i/iiiiiiiiiiiiiiiiiiiiiiiiiiiii/:ii',
|
||||||
|
'/j/:j/jjjjjjjjjjjjjjjjjjjjjjjjjjjjj/:jj',
|
||||||
|
'/::rest',
|
||||||
]
|
]
|
||||||
|
|
||||||
export const allManyRoutes = [
|
export const allManyRoutes = [
|
||||||
|
@ -37,7 +62,7 @@ export const allManyRoutes = [
|
||||||
'/api/categories/:categoryId/properties',
|
'/api/categories/:categoryId/properties',
|
||||||
'/api/categories/:categoryId/values/:props',
|
'/api/categories/:categoryId/values/:props',
|
||||||
'/api/categories/:categoryId',
|
'/api/categories/:categoryId',
|
||||||
'/api/categories/:categoryId/products/:productId',
|
//'/api/categories/:categoryId/products/:productId',
|
||||||
'/api/categories/:categoryId/products/:productId',
|
'/api/categories/:categoryId/products/:productId',
|
||||||
'/api/customers',
|
'/api/customers',
|
||||||
'/api/customers/:id',
|
'/api/customers/:id',
|
||||||
|
@ -61,7 +86,7 @@ export const allManyRoutes = [
|
||||||
'/api/products/:id',
|
'/api/products/:id',
|
||||||
'/api/products/:id/movement',
|
'/api/products/:id/movement',
|
||||||
'/api/products/:id/sub_products/:productId',
|
'/api/products/:id/sub_products/:productId',
|
||||||
'/api/products/:id/sub_products/:productId',
|
//'/api/products/:id/sub_products/:productId',
|
||||||
'/api/products/code/:code',
|
'/api/products/code/:code',
|
||||||
'/api/products/property/:propertyId',
|
'/api/products/property/:propertyId',
|
||||||
'/api/properties',
|
'/api/properties',
|
||||||
|
@ -84,4 +109,5 @@ export const allManyRoutes = [
|
||||||
'/api/works/public',
|
'/api/works/public',
|
||||||
'/api/staff',
|
'/api/staff',
|
||||||
'/api/staff/:id',
|
'/api/staff/:id',
|
||||||
|
'/::rest',
|
||||||
]
|
]
|
||||||
|
|
148
benchmark/ifs.mjs
Normal file
148
benchmark/ifs.mjs
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
import { summary, run, bench } from 'mitata';
|
||||||
|
|
||||||
|
function printCurrentStatus(fn) {
|
||||||
|
let opt = %GetOptimizationStatus(fn)
|
||||||
|
console.log(`${opt.toString(2).padStart(17, '0').split('').join(' ')} (${opt}) ${fn.name}`)
|
||||||
|
}
|
||||||
|
function printStatusHelperText() {
|
||||||
|
console.log(`┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─╸ is function
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───╸ is never optimized
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ │ │ └─────╸ is always optimized
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ │ └───────╸ is maybe deoptimized
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ └─────────╸ is optimized
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ └───────────╸ is optimized by TurboFan
|
||||||
|
│ │ │ │ │ │ │ │ │ │ └─────────────╸ is interpreted
|
||||||
|
│ │ │ │ │ │ │ │ │ └───────────────╸ is marked for optimization
|
||||||
|
│ │ │ │ │ │ │ │ └─────────────────╸ is marked for concurrent optimization
|
||||||
|
│ │ │ │ │ │ │ └───────────────────╸ is optimizing concurrently
|
||||||
|
│ │ │ │ │ │ └─────────────────────╸ is executing
|
||||||
|
│ │ │ │ │ └───────────────────────╸ topmost frame is turbo fanned
|
||||||
|
│ │ │ │ └─────────────────────────╸ lite mode
|
||||||
|
│ │ │ └───────────────────────────╸ marked for deoptimization
|
||||||
|
│ │ └─────────────────────────────╸ baseline
|
||||||
|
│ └───────────────────────────────╸ topmost frame is interpreted
|
||||||
|
└─────────────────────────────────╸ topmost frame is baseline`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warmup (de-optimize `bench()` calls)
|
||||||
|
bench('noop', () => { });
|
||||||
|
bench('noop2', () => { });
|
||||||
|
|
||||||
|
function ifSingle(a) {
|
||||||
|
if (a[0] === 97 && a[1] === 97 && a[2] === 97 && a[3] === 97 && a.length === 4) {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
|
||||||
|
function ifChain(a) {
|
||||||
|
if (a[0] === 97)
|
||||||
|
if (a[1] === 97)
|
||||||
|
if (a[2] === 97)
|
||||||
|
if (a[3] === 97)
|
||||||
|
if (a.length === 4) {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
|
||||||
|
function ifSingleOpt(a) {
|
||||||
|
if (a.length >= 4 && a[0] === 97 && a[1] === 97 && a[2] === 97 && a[3] === 97 && a.length === 4) {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
|
||||||
|
function ifChainOpt(a) {
|
||||||
|
if (a.length >= 4)
|
||||||
|
if (a[0] === 97)
|
||||||
|
if (a[1] === 97)
|
||||||
|
if (a[2] === 97)
|
||||||
|
if (a[3] === 97)
|
||||||
|
if (a.length === 4) {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
|
||||||
|
function ifSingleOptAlt(a) {
|
||||||
|
if (a.length >= 4 && a[0] == 97 && a[1] == 97 && a[2] == 97 && a[3] == 97 && a.length == 4) {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
|
||||||
|
function ifChainOptAlt(a) {
|
||||||
|
if (a.length >= 4)
|
||||||
|
if (a[0] == 97)
|
||||||
|
if (a[1] == 97)
|
||||||
|
if (a[2] == 97)
|
||||||
|
if (a[3] == 97)
|
||||||
|
if (a.length == 4) {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
|
||||||
|
let paths = [
|
||||||
|
[97, 97, 97, 97],
|
||||||
|
[97, 97, 97, 97, 97],
|
||||||
|
[97, 97, 96, 97],
|
||||||
|
[97, 96, 97, 97],
|
||||||
|
[96, 97, 97, 97],
|
||||||
|
[],
|
||||||
|
[98],
|
||||||
|
[97, 97, 97],
|
||||||
|
[97, 97],
|
||||||
|
[97],
|
||||||
|
[97, 97, 96],
|
||||||
|
[97, 96],
|
||||||
|
[96],
|
||||||
|
]
|
||||||
|
|
||||||
|
let paths2 = [
|
||||||
|
[97, 97, 97, 97],
|
||||||
|
[97, 97, 97, 97, 97],
|
||||||
|
[97, 97, 96, 97],
|
||||||
|
[97, 96, 97, 97],
|
||||||
|
[96, 97, 97, 97],
|
||||||
|
[97, 97, 97, 97],
|
||||||
|
[97, 97, 97, 97, 97],
|
||||||
|
[97, 97, 96, 97],
|
||||||
|
[97, 96, 97, 97],
|
||||||
|
[96, 97, 97, 97],
|
||||||
|
[97, 97, 97, 97, 97, 97],
|
||||||
|
[97, 97, 97, 97, 96, 97],
|
||||||
|
[97, 97, 97, 97, 97, 96],
|
||||||
|
]
|
||||||
|
|
||||||
|
let func1 = [ifSingle, ifChain, ifSingleOpt, ifChainOpt, ifSingleOptAlt, ifChainOptAlt];
|
||||||
|
for (let fun of func1) {
|
||||||
|
console.log('-- begin', fun.name)
|
||||||
|
for (var i = 0; i < 1000000; i++) {
|
||||||
|
paths.map(fun)
|
||||||
|
}
|
||||||
|
printCurrentStatus(fun);
|
||||||
|
}
|
||||||
|
printStatusHelperText()
|
||||||
|
|
||||||
|
|
||||||
|
summary(() => {
|
||||||
|
func1.forEach(function(fun) {
|
||||||
|
bench(fun.name + ' first', function() {
|
||||||
|
return paths.map(fun)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
func1.forEach(function(fun) {
|
||||||
|
bench(fun.name + ' second', function() {
|
||||||
|
return paths2.map(fun)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
run().then(function() {
|
||||||
|
for (let fun of func1) {
|
||||||
|
printCurrentStatus(fun);
|
||||||
|
}
|
||||||
|
printStatusHelperText()
|
||||||
|
});
|
85
benchmark/ifs_compile.mjs
Normal file
85
benchmark/ifs_compile.mjs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import { printCurrentStatus, printStatusHelperText } from "./compiler/utils.mjs";
|
||||||
|
|
||||||
|
function ifSingle(a) {
|
||||||
|
if (a[0] === 97 && a[1] === 97 && a[2] === 97 && a[3] === 97 && a.length === 4) {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
|
||||||
|
function ifChain(a) {
|
||||||
|
if (a[0] === 97)
|
||||||
|
if (a[1] === 97)
|
||||||
|
if (a[2] === 97)
|
||||||
|
if (a[3] === 97)
|
||||||
|
if (a.length === 4) {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
|
||||||
|
function ifSingleOptimized(a) {
|
||||||
|
if (a.length >= 4 && a[0] === 97 && a[1] === 97 && a[2] === 97 && a[3] === 97 && a.length === 4) {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
|
||||||
|
function ifChainOptimized(a) {
|
||||||
|
if (a.length >= 4)
|
||||||
|
if (a[0] === 97)
|
||||||
|
if (a[1] === 97)
|
||||||
|
if (a[2] === 97)
|
||||||
|
if (a[3] === 97)
|
||||||
|
if (a.length === 4) {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
|
||||||
|
function ifSingleOptimizedAlt(a) {
|
||||||
|
if (a.length >= 4 && a[0] == 97 && a[1] == 97 && a[2] == 97 && a[3] == 97 && a.length == 4) {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
|
||||||
|
function ifChainOptimizedAlt(a) {
|
||||||
|
if (a.length >= 4)
|
||||||
|
if (a[0] == 97)
|
||||||
|
if (a[1] == 97)
|
||||||
|
if (a[2] == 97)
|
||||||
|
if (a[3] == 97)
|
||||||
|
if (a.length == 4) {
|
||||||
|
return 100
|
||||||
|
}
|
||||||
|
return 10
|
||||||
|
}
|
||||||
|
|
||||||
|
let paths = [
|
||||||
|
[97, 97, 97, 97],
|
||||||
|
[97, 97, 97, 97, 97],
|
||||||
|
[97, 97, 96, 97],
|
||||||
|
[97, 96, 97, 97],
|
||||||
|
[96, 97, 97, 97],
|
||||||
|
[],
|
||||||
|
[98],
|
||||||
|
[97, 97, 97],
|
||||||
|
[97, 97],
|
||||||
|
[97],
|
||||||
|
[97, 97, 96],
|
||||||
|
[97, 96],
|
||||||
|
[96],
|
||||||
|
]
|
||||||
|
|
||||||
|
let func1 = [ifSingle, ifChain, ifSingleOptimized, ifChainOptimized, ifSingleOptimizedAlt, ifChainOptimizedAlt];
|
||||||
|
for (let fun of func1) {
|
||||||
|
console.log('-- begin', fun.name)
|
||||||
|
for (var i = 0; i < 1000000; i++) {
|
||||||
|
paths.map(x => fun(x))
|
||||||
|
}
|
||||||
|
printCurrentStatus(fun);
|
||||||
|
}
|
||||||
|
printStatusHelperText()
|
||||||
|
|
||||||
|
|
106
benchmark/map_query.mjs
Normal file
106
benchmark/map_query.mjs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import { summary, run, bench } from 'mitata';
|
||||||
|
|
||||||
|
function printCurrentStatus(fn) {
|
||||||
|
let opt = %GetOptimizationStatus(fn)
|
||||||
|
console.log(`${opt.toString(2).padStart(17, '0').split('').join(' ')} (${opt}) ${fn.name}`)
|
||||||
|
}
|
||||||
|
function printStatusHelperText() {
|
||||||
|
console.log(`┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─╸ is function
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───╸ is never optimized
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ │ │ └─────╸ is always optimized
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ │ └───────╸ is maybe deoptimized
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ └─────────╸ is optimized
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ └───────────╸ is optimized by TurboFan
|
||||||
|
│ │ │ │ │ │ │ │ │ │ └─────────────╸ is interpreted
|
||||||
|
│ │ │ │ │ │ │ │ │ └───────────────╸ is marked for optimization
|
||||||
|
│ │ │ │ │ │ │ │ └─────────────────╸ is marked for concurrent optimization
|
||||||
|
│ │ │ │ │ │ │ └───────────────────╸ is optimizing concurrently
|
||||||
|
│ │ │ │ │ │ └─────────────────────╸ is executing
|
||||||
|
│ │ │ │ │ └───────────────────────╸ topmost frame is turbo fanned
|
||||||
|
│ │ │ │ └─────────────────────────╸ lite mode
|
||||||
|
│ │ │ └───────────────────────────╸ marked for deoptimization
|
||||||
|
│ │ └─────────────────────────────╸ baseline
|
||||||
|
│ └───────────────────────────────╸ topmost frame is interpreted
|
||||||
|
└─────────────────────────────────╸ topmost frame is baseline`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warmup (de-optimize `bench()` calls)
|
||||||
|
bench('noop', () => { });
|
||||||
|
bench('noop2', () => { });
|
||||||
|
|
||||||
|
function mapGetAndCheck(map, t) {
|
||||||
|
let x = map.get(t)
|
||||||
|
if (x) return x
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapCheckBeforeGet(map, t) {
|
||||||
|
if (map.has(t)) {
|
||||||
|
return map.get(t)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
let paths = [
|
||||||
|
'a',
|
||||||
|
'aa',
|
||||||
|
'aaa',
|
||||||
|
'aaaa',
|
||||||
|
'aaaaa',
|
||||||
|
'aaaaaa',
|
||||||
|
'aaaaaaa',
|
||||||
|
'aaaaaaaa',
|
||||||
|
]
|
||||||
|
let tests = [
|
||||||
|
'a',
|
||||||
|
'aa',
|
||||||
|
'aaa',
|
||||||
|
'aaaa',
|
||||||
|
'aaaaa',
|
||||||
|
'aaaaaa',
|
||||||
|
'aaaaaaa',
|
||||||
|
'aaaaaaaa',
|
||||||
|
'b',
|
||||||
|
'bb',
|
||||||
|
'bbb',
|
||||||
|
'bbbb',
|
||||||
|
'bbbbb',
|
||||||
|
'bbbbbb',
|
||||||
|
'bbbbbbb',
|
||||||
|
'bbbbbbbb',
|
||||||
|
'c',
|
||||||
|
'cc',
|
||||||
|
'ccc',
|
||||||
|
'cccc',
|
||||||
|
'ccccc',
|
||||||
|
'cccccc',
|
||||||
|
'ccccccc',
|
||||||
|
'cccccccc',
|
||||||
|
]
|
||||||
|
|
||||||
|
let map = new Map(paths.map(x => [x, { a: x }]))
|
||||||
|
|
||||||
|
let func1 = [mapGetAndCheck, mapCheckBeforeGet];
|
||||||
|
for (let fun of func1) {
|
||||||
|
console.log('-- begin', fun.name)
|
||||||
|
for (var i = 0; i < 1000000; i++) {
|
||||||
|
tests.map(t => fun(map, t))
|
||||||
|
}
|
||||||
|
printCurrentStatus(fun);
|
||||||
|
}
|
||||||
|
printStatusHelperText()
|
||||||
|
|
||||||
|
summary(() => {
|
||||||
|
func1.forEach(function(fun) {
|
||||||
|
bench(fun.name, function() {
|
||||||
|
return paths.map(t => fun(map, t))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
run().then(function() {
|
||||||
|
for (let fun of func1) {
|
||||||
|
printCurrentStatus(fun);
|
||||||
|
}
|
||||||
|
printStatusHelperText()
|
||||||
|
});
|
25
benchmark/mapl_compiler.mjs
Normal file
25
benchmark/mapl_compiler.mjs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { getExternalKeys } from '@mapl/compiler';
|
||||||
|
import { compileRouter as compileRouterContent } from '@mapl/router';
|
||||||
|
const PATH = "__req_p";
|
||||||
|
const REQ = '__req';
|
||||||
|
const PARAMS = `${REQ}_ps`;
|
||||||
|
|
||||||
|
export function compileRouter(root) {
|
||||||
|
const state = {
|
||||||
|
contentBuilder: [],
|
||||||
|
declarationBuilders: [],
|
||||||
|
externalValues: [],
|
||||||
|
|
||||||
|
compileItem: (item, state, hasParam) => {
|
||||||
|
state.contentBuilder.push(`return [f${state.externalValues.push(item)},${hasParam ? PARAMS : '[]'}];`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
compileRouterContent(root, state);
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
return Function(
|
||||||
|
...getExternalKeys(state),
|
||||||
|
`return (${PATH})=>{${state.contentBuilder.join('')}return null;}`
|
||||||
|
)(...state.externalValues);
|
||||||
|
}
|
31
benchmark/mapl_router_compile.mjs
Normal file
31
benchmark/mapl_router_compile.mjs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { createRouter, insertItem } from '@mapl/router'
|
||||||
|
import { compileRouter } from './mapl_compiler.mjs'
|
||||||
|
import { printTime } from './utils.mjs'
|
||||||
|
import * as consts from './const.js'
|
||||||
|
|
||||||
|
let testPaths = consts.allManyRoutes // consts.allManyRoutes
|
||||||
|
|
||||||
|
/*testPaths = [
|
||||||
|
'/api/works/:id/lock',
|
||||||
|
'/api/staff/:id',
|
||||||
|
// '/::rest',
|
||||||
|
]*/
|
||||||
|
|
||||||
|
testPaths = testPaths.map(x => x.replace(/::[^\/]+/g, '**').replace(/:[^\/]+/g, '*'))
|
||||||
|
|
||||||
|
const r = createRouter();
|
||||||
|
for (let route of testPaths) {
|
||||||
|
let cleaned = route.replace(/:[^\/]+/g, '*')
|
||||||
|
insertItem(r, cleaned, () => { })
|
||||||
|
}
|
||||||
|
|
||||||
|
let s1 = process.hrtime.bigint()
|
||||||
|
let s2 = process.hrtime.bigint()
|
||||||
|
|
||||||
|
compileRouter(r);
|
||||||
|
|
||||||
|
let s3 = process.hrtime.bigint()
|
||||||
|
|
||||||
|
let time = s3 - s2 - (s2 - s1)
|
||||||
|
|
||||||
|
printTime(time)
|
1320
benchmark/package-lock.json
generated
1320
benchmark/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -11,8 +11,10 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "WTFPL",
|
"license": "WTFPL",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@mapl/router": "^0.0.16",
|
||||||
"benchmarkjs-pretty": "^2.0.0",
|
"benchmarkjs-pretty": "^2.0.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"koa-router": "^8.0.8"
|
"koa-router": "^8.0.8",
|
||||||
|
"mitata": "^1.0.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
221
benchmark/promise.mjs
Normal file
221
benchmark/promise.mjs
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
import { summary, run, bench } from 'mitata';
|
||||||
|
|
||||||
|
// Warmup (de-optimize `bench()` calls)
|
||||||
|
bench('noop', () => { });
|
||||||
|
bench('noop2', () => { });
|
||||||
|
|
||||||
|
// Example benchmark
|
||||||
|
/*summary(() => {
|
||||||
|
const fn = () => null
|
||||||
|
const fnAlt = function() {}
|
||||||
|
function fnBasic() {}
|
||||||
|
const fnAltWithReturn = function() { return null }
|
||||||
|
function fnBasicWithReturn() { return null }
|
||||||
|
|
||||||
|
bench('Reject with uncached error handler', async () => await Promise.reject().catch(() => null));
|
||||||
|
bench('Reject with cached error handler', async () => await Promise.reject().catch(fn));
|
||||||
|
|
||||||
|
bench('TT Reject with uncached error handler', () => Promise.reject().catch(() => null));
|
||||||
|
bench('TT Reject with cached error handler', () => Promise.reject().catch(fnAlt));
|
||||||
|
bench('TT Reject with cached basic error handler', () => Promise.reject().catch(fnBasic));
|
||||||
|
bench('TT Reject with cached error handler with return', () => Promise.reject().catch(fnAltWithReturn));
|
||||||
|
bench('TT Reject with cached basic error handler with return ', () => Promise.reject().catch(fnBasicWithReturn));
|
||||||
|
});*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
summary(() => {
|
||||||
|
function triple () {
|
||||||
|
const bla = Math.round(Math.random()) ? true : null
|
||||||
|
return bla === null
|
||||||
|
}
|
||||||
|
function doubleNull () {
|
||||||
|
const bla = Math.round(Math.random()) ? true : null
|
||||||
|
return bla == null
|
||||||
|
}
|
||||||
|
function doubleUndefined () {
|
||||||
|
const bla = Math.round(Math.random()) ? true : undefined
|
||||||
|
return bla == null
|
||||||
|
}
|
||||||
|
bench('null === null', triple);
|
||||||
|
bench('null == null', doubleNull);
|
||||||
|
bench('undefined == null', doubleUndefined);
|
||||||
|
|
||||||
|
bench('2x null === null', triple);
|
||||||
|
bench('2x null == null', doubleNull);
|
||||||
|
bench('2x undefined == null', doubleUndefined);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
summary(() => {
|
||||||
|
function triple () {
|
||||||
|
const bla = false ? true : null
|
||||||
|
return bla === null
|
||||||
|
}
|
||||||
|
function doubleNull () {
|
||||||
|
const bla = false ? true : null
|
||||||
|
return bla == null
|
||||||
|
}
|
||||||
|
function doubleUndefined () {
|
||||||
|
const bla = false ? true : undefined
|
||||||
|
return bla == null
|
||||||
|
}
|
||||||
|
bench('const null === null', triple);
|
||||||
|
bench('const null == null', doubleNull);
|
||||||
|
bench('const undefined == null', doubleUndefined);
|
||||||
|
|
||||||
|
bench('const 2x null === null', triple);
|
||||||
|
bench('const 2x null == null', doubleNull);
|
||||||
|
bench('const 2x undefined == null', doubleUndefined);
|
||||||
|
})*/
|
||||||
|
|
||||||
|
function printCurrentStatus(fn) {
|
||||||
|
let opt = %GetOptimizationStatus(fn)
|
||||||
|
console.log(`${fn.toString()}
|
||||||
|
${opt.toString(2).padStart(12, '0').split('').join(' ')}`)
|
||||||
|
}
|
||||||
|
function printTree() {
|
||||||
|
console.log(`┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ └─╸ is function
|
||||||
|
│ │ │ │ │ │ │ │ │ │ └───╸ is never optimized
|
||||||
|
│ │ │ │ │ │ │ │ │ └─────╸ is always optimized
|
||||||
|
│ │ │ │ │ │ │ │ └───────╸ is maybe deoptimized
|
||||||
|
│ │ │ │ │ │ │ └─────────╸ is optimized
|
||||||
|
│ │ │ │ │ │ └───────────╸ is optimized by TurboFan
|
||||||
|
│ │ │ │ │ └─────────────╸ is interpreted
|
||||||
|
│ │ │ │ └───────────────╸ is marked for optimization
|
||||||
|
│ │ │ └─────────────────╸ is marked for concurrent optimization
|
||||||
|
│ │ └───────────────────╸ is optimizing concurrently
|
||||||
|
│ └─────────────────────╸ is executing
|
||||||
|
└───────────────────────╸ topmost frame is turbo fanned`)
|
||||||
|
}
|
||||||
|
|
||||||
|
let func1
|
||||||
|
let func2
|
||||||
|
let func3
|
||||||
|
|
||||||
|
await (async function () {
|
||||||
|
return
|
||||||
|
const fn1 = async () => await Promise.reject().catch(() => null) === null ? 1 : 0;
|
||||||
|
const fn2 = async () => await Promise.reject().catch(() => {}) == null ? 1 : 0;
|
||||||
|
const fn3 = async () => await Promise.reject().catch(() => null) == null ? 1 : 0;
|
||||||
|
const noop = () => null;
|
||||||
|
const fn4 = async () => await Promise.reject().catch(noop) == null ? 1 : 0;
|
||||||
|
const fn5 = async () => await Promise.reject().catch(noop) === null ? 1 : 0;
|
||||||
|
|
||||||
|
|
||||||
|
const fn6 = () => Promise.reject().catch(() => null).then(x => x === null ? 1 : 0);
|
||||||
|
const fn7 = () => Promise.reject().catch(() => {}).then(x => x == null ? 1 : 0);
|
||||||
|
const fn8 = () => Promise.reject().catch(() => null).then(x => x == null ? 1 : 0);
|
||||||
|
const fn9 = () => Promise.reject().catch(noop).then(x => x == null ? 1 : 0);
|
||||||
|
const fn10 = () => Promise.reject().catch(noop).then(x => x === null ? 1 : 0);
|
||||||
|
|
||||||
|
func1 = [fn1, fn2, fn3, fn4, fn5, fn6, fn7, fn8, fn9, fn10];
|
||||||
|
for (let fun of func1) {
|
||||||
|
%OptimizeFunctionOnNextCall(fun);
|
||||||
|
}
|
||||||
|
for (var i = 0; i < 100000; i++) {
|
||||||
|
for (let fun of func1) {
|
||||||
|
await fun()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let fun of func1) {
|
||||||
|
printCurrentStatus(fun);
|
||||||
|
}
|
||||||
|
printTree()
|
||||||
|
|
||||||
|
summary(() => {
|
||||||
|
bench('Null with ===', fn1);
|
||||||
|
bench('Undefined with ==', fn2);
|
||||||
|
bench('Null with ==', fn3);
|
||||||
|
bench('Cached Null with ==', fn4);
|
||||||
|
bench('Cached Null with ===', fn5);
|
||||||
|
|
||||||
|
|
||||||
|
bench('[sync] Null with ===', fn6);
|
||||||
|
bench('[sync] Undefined with ==', fn7);
|
||||||
|
bench('[sync] Null with ==', fn8);
|
||||||
|
bench('[sync] Cached Null with ==', fn9);
|
||||||
|
bench('[sync] Cached Null with ===', fn10);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
await (async function () {
|
||||||
|
const alt1 = function () { (Math.round(Math.random()) ? null : 1) ?? 0 };
|
||||||
|
const alt2 = function () { (Math.round(Math.random()) ? null : 1) || 0 };
|
||||||
|
const alt3 = function () { (Math.round(Math.random()) ? undefined : 1) ?? 0 };
|
||||||
|
const alt4 = function () { (Math.round(Math.random()) ? undefined : 1) || 0 };
|
||||||
|
|
||||||
|
let check = new Array(100).fill(0).map(() => Math.round(Math.random()) ? null : 1)
|
||||||
|
let check2 = new Array(100).fill(0).map(() => Math.round(Math.random()) ? undefined : 1)
|
||||||
|
|
||||||
|
const alt5 = function () { let out = 0; for (let x of check) { out += x ?? 0 } return out };
|
||||||
|
const alt6 = function () { let out = 0; for (let x of check) { out += x || 0 } return out };
|
||||||
|
const alt7 = function () { let out = 0; for (let x of check2) { out += x ?? 0 } return out };
|
||||||
|
const alt8 = function () { let out = 0; for (let x of check2) { out += x || 0 } return out };
|
||||||
|
|
||||||
|
func2 = [alt1, alt2, alt3, alt4, alt5, alt6, alt7, alt8];
|
||||||
|
for (let fun of func2) {
|
||||||
|
%OptimizeFunctionOnNextCall(fun);
|
||||||
|
}
|
||||||
|
for (var i = 0; i < 100000; i++) {
|
||||||
|
for (let fun of func2) {
|
||||||
|
fun()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let fun of func2) {
|
||||||
|
printCurrentStatus(fun);
|
||||||
|
}
|
||||||
|
printTree()
|
||||||
|
|
||||||
|
summary(() => {
|
||||||
|
bench('null/1 ?? 0', alt1);
|
||||||
|
bench('null/1 || 0', alt2);
|
||||||
|
bench('undefined/1 ?? 0', alt3);
|
||||||
|
bench('undefined/1 || 0', alt4);
|
||||||
|
});
|
||||||
|
|
||||||
|
summary(() => {
|
||||||
|
bench('arr null/1 ?? null', alt5);
|
||||||
|
bench('arr null/1 || null', alt6);
|
||||||
|
bench('arr und/1 ?? null', alt7);
|
||||||
|
bench('arr und/1 || null', alt8);
|
||||||
|
});
|
||||||
|
})()
|
||||||
|
|
||||||
|
|
||||||
|
await (async function () {
|
||||||
|
const alt1 = function () { let out = 0; for (let x = 0; x < 100; x++) { out += (Math.round(Math.random()) ? null : 1) ?? 0 } return out };
|
||||||
|
const alt2 = function () { let out = 0; for (let x = 0; x < 100; x++) { out += (Math.round(Math.random()) ? null : 1) || 0 } return out };
|
||||||
|
const alt3 = function () { let out = 0; for (let x = 0; x < 100; x++) { out += (Math.round(Math.random()) ? undefined : 1) ?? 0 } return out };
|
||||||
|
const alt4 = function () { let out = 0; for (let x = 0; x < 100; x++) { out += (Math.round(Math.random()) ? undefined : 1) || 0 } return out };
|
||||||
|
|
||||||
|
func3 = [alt1, alt2, alt3, alt4];
|
||||||
|
for (let fun of func3) {
|
||||||
|
%OptimizeFunctionOnNextCall(fun);
|
||||||
|
}
|
||||||
|
for (var i = 0; i < 100000; i++) {
|
||||||
|
for (let fun of func3) {
|
||||||
|
fun()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let fun of func3) {
|
||||||
|
printCurrentStatus(fun);
|
||||||
|
}
|
||||||
|
printTree()
|
||||||
|
|
||||||
|
summary(() => {
|
||||||
|
bench('loop null/1 ?? 0', alt1);
|
||||||
|
bench('loop null/1 || 0', alt2);
|
||||||
|
bench('loop und/1 ?? 0', alt3);
|
||||||
|
bench('loop und/1 || 0', alt4);
|
||||||
|
});
|
||||||
|
})()
|
||||||
|
|
||||||
|
// Start the benchmark
|
||||||
|
run().then(() => {
|
||||||
|
for (let fun of func3) {
|
||||||
|
printCurrentStatus(fun);
|
||||||
|
}
|
||||||
|
printTree()
|
||||||
|
});
|
46
benchmark/random.js
Normal file
46
benchmark/random.js
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import crypto from 'crypto'
|
||||||
|
import Benchmark from 'benchmarkjs-pretty'
|
||||||
|
|
||||||
|
function TestGenerateRandomString() {
|
||||||
|
|
||||||
|
return new Benchmark.default('test different method to generate random string)')
|
||||||
|
.add('crypto.randomBytes(16)', function() {
|
||||||
|
for (let i = 0; i < 25; i++) {
|
||||||
|
crypto.randomBytes(16).toString('base64')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.add('crypto.randomBytes(32)', function() {
|
||||||
|
for (let i = 0; i < 25; i++) {
|
||||||
|
crypto.randomBytes(32).toString('base64')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.add('random (11 characters long)', function() {
|
||||||
|
for (let i = 0; i < 25; i++) {
|
||||||
|
let out = Math.random().toString(36).substring(2, 14)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.add('random (22 characters long)', function() {
|
||||||
|
for (let i = 0; i < 25; i++) {
|
||||||
|
let out = Math.random().toString(36).substring(2, 24)
|
||||||
|
+ Math.random().toString(36).substring(2, 24)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.add('random (44 characters long)', function() {
|
||||||
|
for (let i = 0; i < 25; i++) {
|
||||||
|
let out = Math.random().toString(36).substring(2, 24)
|
||||||
|
+ Math.random().toString(36).substring(2, 24)
|
||||||
|
+ Math.random().toString(36).substring(2, 24)
|
||||||
|
+ Math.random().toString(36).substring(2, 24)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.run()
|
||||||
|
.then(function() {}, function(e) {
|
||||||
|
console.error('error:', e)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
TestGenerateRandomString()
|
||||||
|
.then(function() {
|
||||||
|
process.exit(0)
|
||||||
|
})
|
97
benchmark/router_flaska_v2.mjs
Normal file
97
benchmark/router_flaska_v2.mjs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
import { summary, run, bench } from 'mitata';
|
||||||
|
import { createRouter, insertItem } from '@mapl/router'
|
||||||
|
import { compileRouter } from './mapl_compiler.mjs'
|
||||||
|
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 * as consts from './const.js'
|
||||||
|
|
||||||
|
function printCurrentStatus(fn) {
|
||||||
|
// console.log(`--- checking optimizations status on ${fn.name} ---`)
|
||||||
|
// let opt = %GetOptimizationStatus(fn)
|
||||||
|
// console.log(`${opt.toString(2).padStart(17, '0').split('').join(' ')} (${opt}) ${fn.name}`)
|
||||||
|
}
|
||||||
|
function printStatusHelperText() {
|
||||||
|
console.log(`┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─╸ is function
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───╸ is never optimized
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ │ │ └─────╸ is always optimized
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ │ └───────╸ is maybe deoptimized
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ │ └─────────╸ is optimized
|
||||||
|
│ │ │ │ │ │ │ │ │ │ │ └───────────╸ is optimized by TurboFan
|
||||||
|
│ │ │ │ │ │ │ │ │ │ └─────────────╸ is interpreted
|
||||||
|
│ │ │ │ │ │ │ │ │ └───────────────╸ is marked for optimization
|
||||||
|
│ │ │ │ │ │ │ │ └─────────────────╸ is marked for concurrent optimization
|
||||||
|
│ │ │ │ │ │ │ └───────────────────╸ is optimizing concurrently
|
||||||
|
│ │ │ │ │ │ └─────────────────────╸ is executing
|
||||||
|
│ │ │ │ │ └───────────────────────╸ topmost frame is turbo fanned
|
||||||
|
│ │ │ │ └─────────────────────────╸ lite mode
|
||||||
|
│ │ │ └───────────────────────────╸ marked for deoptimization
|
||||||
|
│ │ └─────────────────────────────╸ baseline
|
||||||
|
│ └───────────────────────────────╸ topmost frame is interpreted
|
||||||
|
└─────────────────────────────────╸ topmost frame is baseline`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warmup (de-optimize `bench()` calls)
|
||||||
|
bench('noop', () => { });
|
||||||
|
bench('noop2', () => { });
|
||||||
|
|
||||||
|
let paths = consts.allRoutes.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 maplPaths = paths.map(x => x.path.replace(/::[^\/]+/g, '**').replace(/:[^\/]+/g, '*'))
|
||||||
|
const maplRouter = createRouter();
|
||||||
|
for (let route of maplPaths) {
|
||||||
|
insertItem(maplRouter, route, { path: route })
|
||||||
|
}
|
||||||
|
let maplMatcher = compileRouter(maplRouter)
|
||||||
|
|
||||||
|
func.push([testStringsMapl, maplMatcher])
|
||||||
|
|
||||||
|
for (let [tests, fun] of func) {
|
||||||
|
console.log(`--- warming up ${fun.name || 'mapl'} ---`)
|
||||||
|
for (var i = 0; i < 100000; i++) {
|
||||||
|
tests.forEach(fun)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let [tests, fun] of func) {
|
||||||
|
console.log(`--- Sanity checking ${fun.name || 'mapl'} ---`)
|
||||||
|
for (let a of tests) {
|
||||||
|
// console.log(a, fun(a))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('--- sleeping ---')
|
||||||
|
await new Promise(res => setTimeout(res, 1000))
|
||||||
|
for (let [_, org] of func) {
|
||||||
|
printCurrentStatus(org);
|
||||||
|
}
|
||||||
|
printStatusHelperText()
|
||||||
|
|
||||||
|
summary(() => {
|
||||||
|
func.forEach(function([tests, fun]) {
|
||||||
|
// console.log(tests, fun, tests.map(fun))
|
||||||
|
bench(fun.name || 'mapl', function() {
|
||||||
|
return tests.map(fun)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
run().then(function() {
|
||||||
|
for (let [_, check] of func) {
|
||||||
|
printCurrentStatus(check);
|
||||||
|
}
|
||||||
|
printStatusHelperText()
|
||||||
|
});
|
517
benchmark/router_v2.mjs
Normal file
517
benchmark/router_v2.mjs
Normal file
|
@ -0,0 +1,517 @@
|
||||||
|
const Debug = false
|
||||||
|
|
||||||
|
export function printTree(children, indent = 0) {
|
||||||
|
if (!children.length || !Debug) return
|
||||||
|
|
||||||
|
for (let child of children) {
|
||||||
|
console.log(''.padStart(indent * 2) + (child.char || '*')
|
||||||
|
+ ' ' + (child.isParams ? `{ params = ${child.isParams} }` : '')
|
||||||
|
+ (child.isFullParams ? `{ fullParams = ${child.isFullParams} }` : '')
|
||||||
|
+ ' ' + (child.path ? `{ handler = ${child.path.path} }` : ''))
|
||||||
|
printTree(child.children, indent + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = []
|
||||||
|
}
|
||||||
|
|
||||||
|
function 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(buildChild(x, i + 1, consume))
|
||||||
|
consume.splice(0, letter.children[letter.children.length - 1].count)
|
||||||
|
}
|
||||||
|
return letter
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildTree(splitPaths) {
|
||||||
|
let builder = []
|
||||||
|
while (splitPaths.length) {
|
||||||
|
builder.push(buildChild(0, 0, splitPaths))
|
||||||
|
splitPaths.splice(0, builder[builder.length - 1].count)
|
||||||
|
}
|
||||||
|
return builder
|
||||||
|
}
|
||||||
|
|
||||||
|
const regParamPrefix = /^::?/
|
||||||
|
const regCleanNonAschii = /(?![a-zA-Z_])./g
|
||||||
|
const regCleanRest = /_+/g
|
||||||
|
|
||||||
|
function 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
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
throw new RouterError(aGroup, bGroup, 'Two identical paths were found')
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
staticPaths,
|
||||||
|
paramsPaths,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const SlashCode = '/'.charCodeAt(0)
|
||||||
|
|
||||||
|
function getIndex(offset, additions, params) {
|
||||||
|
return (offset + additions)
|
||||||
|
+ (params.length
|
||||||
|
? ' + ' + params.map(a => `offset${a[1]}`).join(' + ')
|
||||||
|
: '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function treeIntoCompiledTreeBufferReturnPath(indentString, paths, branch, params, alt) {
|
||||||
|
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 = ''
|
||||||
|
output += '\n' + indentString + `return {`
|
||||||
|
output += '\n' + indentString + ` path: paths[${pathIndex}],`
|
||||||
|
if (params.length) {
|
||||||
|
output += '\n' + indentString + ` params: {`
|
||||||
|
for (let param of params) {
|
||||||
|
if (alt === 0) {
|
||||||
|
output += '\n' + indentString + ` ${param[0]}: s${param[1]}.toString(),`
|
||||||
|
} else if (alt === 1 || alt === 3) {
|
||||||
|
output += '\n' + indentString + ` ${param[0]}: s${param[1]},`
|
||||||
|
} else if (alt === 2) {
|
||||||
|
output += '\n' + indentString + ` ${param[0]}: textDecoder.decode(s${param[1]}),`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output += '\n' + indentString + ` },`
|
||||||
|
} else {
|
||||||
|
output += '\n' + indentString + ` params: {},`
|
||||||
|
}
|
||||||
|
output += '\n' + indentString + `}`
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function treeIntoCompiledTreeBufferBranch(paths, branches, indent = 0, params = [], alt = 0) {
|
||||||
|
let output = ''
|
||||||
|
let indentation = ''.padStart((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 '
|
||||||
|
} else {
|
||||||
|
// output += '} //'
|
||||||
|
output += '\n' + indentation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!branch.isParams && !branch.isFullParams) {
|
||||||
|
output += `if (buf[${getIndex(indent, 0, params)}] === ${branch.char.charCodeAt(0)}) { // ${branch.char}`
|
||||||
|
|
||||||
|
if (branch.path) {
|
||||||
|
output += '\n' + indentation + ` if (buf.length === ${getIndex(indent, 1, params)}) {`
|
||||||
|
output += treeIntoCompiledTreeBufferReturnPath(indentation + ' ', paths, branch, params, alt)
|
||||||
|
output += '\n' + indentation + ` }`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addEndBracket = false
|
||||||
|
let paramVarName = (params.length + 1) + '_' + branch.paramVarName
|
||||||
|
if (alt === 1 || alt === 3) {
|
||||||
|
output += `let s${paramVarName} = str.slice(${getIndex(indent, 0, params)}${branch.isFullParams ? '' : `, buf.indexOf(${SlashCode}, ${getIndex(indent, 0, params)}) >>> 0`})`
|
||||||
|
} else {
|
||||||
|
output += `let s${paramVarName} = buf.slice(${getIndex(indent, 0, params)}${branch.isFullParams ? '' : `, buf.indexOf(${SlashCode}, ${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 += treeIntoCompiledTreeBufferReturnPath(indentation, paths, branch, params, alt)
|
||||||
|
} else if (branch.path) {
|
||||||
|
output += '\n' + indentation + `if (buf.length === ${getIndex(indent, 0, params)}) {`
|
||||||
|
output += treeIntoCompiledTreeBufferReturnPath(indentation + ' ', paths, branch, params, alt)
|
||||||
|
output += '\n' + indentation + `}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (branch.children.length) {
|
||||||
|
if (branch.path) {
|
||||||
|
output += ' else '
|
||||||
|
} else {
|
||||||
|
output += '\n' + indentation + ' '
|
||||||
|
}
|
||||||
|
output += treeIntoCompiledTreeBufferBranch(paths, branch.children, indent + 1, params.slice())
|
||||||
|
}
|
||||||
|
if (addEndBracket) {
|
||||||
|
output += '\n' + indentation + '} '
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
export function treeIntoCompiledTreeBuffer(paths, tree) {
|
||||||
|
let output = 'return function RBuffer(paths, static, str) {'
|
||||||
|
output += '\n let checkStatic = static.get(str)'
|
||||||
|
output += '\n if(checkStatic) {'
|
||||||
|
output += '\n return checkStatic'
|
||||||
|
output += '\n }'
|
||||||
|
output += '\n var buf = Buffer.from(str)'
|
||||||
|
output += '\n ' + treeIntoCompiledTreeBufferBranch(paths, tree, 1)
|
||||||
|
output += '\n return null'
|
||||||
|
output += '\n}'
|
||||||
|
if (Debug) {
|
||||||
|
console.log(output)
|
||||||
|
}
|
||||||
|
return new Function(output)()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function treeIntoCompiledTreeStringReturnPath(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 = ''
|
||||||
|
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]},`
|
||||||
|
}
|
||||||
|
output += '\n' + indentString + ` },`
|
||||||
|
} else {
|
||||||
|
output += '\n' + indentString + ` params: {},`
|
||||||
|
}
|
||||||
|
output += '\n' + indentString + `}`
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function treeIntoCompiledTreeStringBranch(paths, branches, indent = 0, params = []) {
|
||||||
|
let output = ''
|
||||||
|
let indentation = ''.padStart((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 '
|
||||||
|
} else {
|
||||||
|
// output += '} //'
|
||||||
|
output += '\n' + indentation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!branch.isParams && !branch.isFullParams) {
|
||||||
|
output += `if (str.charCodeAt(${getIndex(indent, 0, params)}) === ${branch.char.charCodeAt(0)}) { // ${branch.char}`
|
||||||
|
|
||||||
|
if (branch.path) {
|
||||||
|
output += '\n' + indentation + ` if (str.length === ${getIndex(indent, 1, params)}) {`
|
||||||
|
output += treeIntoCompiledTreeStringReturnPath(indentation + ' ', paths, branch, params)
|
||||||
|
output += '\n' + indentation + ` }`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addEndBracket = false
|
||||||
|
let paramVarName = (params.length + 1) + '_' + branch.paramVarName
|
||||||
|
output += `let s${paramVarName} = str.slice(${getIndex(indent, 0, params)}${branch.isFullParams ? '' : `, str.indexOf('/', ${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 += treeIntoCompiledTreeStringReturnPath(indentation, paths, branch, params)
|
||||||
|
} else if (branch.path) {
|
||||||
|
output += '\n' + indentation + `if (str.length === ${getIndex(indent, 0, params)}) {`
|
||||||
|
output += treeIntoCompiledTreeStringReturnPath(indentation + ' ', paths, branch, params)
|
||||||
|
output += '\n' + indentation + `}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (branch.children.length) {
|
||||||
|
if (branch.path) {
|
||||||
|
output += ' else '
|
||||||
|
} else {
|
||||||
|
output += '\n' + indentation + ' '
|
||||||
|
}
|
||||||
|
output += treeIntoCompiledTreeStringBranch(paths, branch.children, indent + 1, params.slice())
|
||||||
|
}
|
||||||
|
if (addEndBracket) {
|
||||||
|
output += '\n' + indentation + '} '
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
function treeIntoCompiledTreeStringBranchIndices(paths, branches, indent = 0, params = []) {
|
||||||
|
let output = ''
|
||||||
|
let indentation = ''.padStart((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 '
|
||||||
|
} else {
|
||||||
|
// output += '} //'
|
||||||
|
output += '\n' + indentation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!branch.isParams && !branch.isFullParams) {
|
||||||
|
output += `if (str[${getIndex(indent, 0, params)}] === '${branch.char}') { // ${branch.char}`
|
||||||
|
|
||||||
|
if (branch.path) {
|
||||||
|
output += '\n' + indentation + ` if (str.length === ${getIndex(indent, 1, params)}) {`
|
||||||
|
output += treeIntoCompiledTreeStringReturnPath(indentation + ' ', paths, branch, params)
|
||||||
|
output += '\n' + indentation + ` }`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addEndBracket = false
|
||||||
|
let paramVarName = (params.length + 1) + '_' + branch.paramVarName
|
||||||
|
output += `let s${paramVarName} = str.slice(${getIndex(indent, 0, params)}${branch.isFullParams ? '' : `, str.indexOf('/', ${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 += treeIntoCompiledTreeStringReturnPath(indentation, paths, branch, params)
|
||||||
|
} else if (branch.path) {
|
||||||
|
output += '\n' + indentation + `if (str.length === ${getIndex(indent, 0, params)}) {`
|
||||||
|
output += treeIntoCompiledTreeStringReturnPath(indentation + ' ', paths, branch, params)
|
||||||
|
output += '\n' + indentation + `}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (branch.children.length) {
|
||||||
|
if (branch.path) {
|
||||||
|
output += ' else '
|
||||||
|
} else {
|
||||||
|
output += '\n' + indentation + ' '
|
||||||
|
}
|
||||||
|
output += treeIntoCompiledTreeStringBranch(paths, branch.children, indent + 1, params.slice())
|
||||||
|
}
|
||||||
|
if (addEndBracket) {
|
||||||
|
output += '\n' + indentation + '} '
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
export function treeIntoCompiledTreeString(paths, tree) {
|
||||||
|
let output = 'return function RString(paths, static, str) {'
|
||||||
|
output += '\n let checkStatic = static.get(str)'
|
||||||
|
output += '\n if(checkStatic) {'
|
||||||
|
output += '\n return checkStatic'
|
||||||
|
output += '\n }'
|
||||||
|
output += '\n ' + treeIntoCompiledTreeStringBranch(paths, tree, 1)
|
||||||
|
output += '\n return null'
|
||||||
|
output += '\n}'
|
||||||
|
if (Debug) {
|
||||||
|
console.log(output)
|
||||||
|
}
|
||||||
|
return new Function(output)()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function treeIntoCompiledTreeStringIndices(paths, tree) {
|
||||||
|
let output = 'return function RStringIndices(paths, static, str) {'
|
||||||
|
output += '\n let checkStatic = static.get(str)'
|
||||||
|
output += '\n if(checkStatic) {'
|
||||||
|
output += '\n return checkStatic'
|
||||||
|
output += '\n }'
|
||||||
|
output += '\n ' + treeIntoCompiledTreeStringBranchIndices(paths, tree, 1)
|
||||||
|
output += '\n return null'
|
||||||
|
output += '\n}'
|
||||||
|
if (Debug) {
|
||||||
|
console.log(output)
|
||||||
|
}
|
||||||
|
return new Function(output)()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function treeIntoCompiledTreeBufferAltOne(paths, tree) {
|
||||||
|
let output = 'return function RBufferStrSlice(paths, static, str) {'
|
||||||
|
output += '\n let checkStatic = static.get(str)'
|
||||||
|
output += '\n if(checkStatic) {'
|
||||||
|
output += '\n return checkStatic'
|
||||||
|
output += '\n }'
|
||||||
|
output += '\n var buf = Buffer.from(str)'
|
||||||
|
output += '\n ' + treeIntoCompiledTreeBufferBranch(paths, tree, 1, [], 1)
|
||||||
|
output += '\n return null'
|
||||||
|
output += '\n}'
|
||||||
|
if (Debug) {
|
||||||
|
console.log(output)
|
||||||
|
}
|
||||||
|
return new Function(output)()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function treeIntoCompiledTreeBufferAltTwo(paths, tree) {
|
||||||
|
let output = 'return function RTextEncoderDecoder(textEncoder, textDecoder, paths, static, str) {'
|
||||||
|
output += '\n let checkStatic = static.get(str)'
|
||||||
|
output += '\n if(checkStatic) {'
|
||||||
|
output += '\n return checkStatic'
|
||||||
|
output += '\n }'
|
||||||
|
output += '\n var buf = textEncoder.encode(str)'
|
||||||
|
output += '\n ' + treeIntoCompiledTreeBufferBranch(paths, tree, 1, [], 2)
|
||||||
|
output += '\n return null'
|
||||||
|
output += '\n}'
|
||||||
|
if (Debug) {
|
||||||
|
console.log(output)
|
||||||
|
}
|
||||||
|
return new Function(output)()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function treeIntoCompiledTreeBufferAltThree(paths, tree) {
|
||||||
|
let output = 'return function RTextEncoderStrSlice(textEncoder, paths, static, str) {'
|
||||||
|
output += '\n let checkStatic = static.get(str)'
|
||||||
|
output += '\n if(checkStatic) {'
|
||||||
|
output += '\n return checkStatic'
|
||||||
|
output += '\n }'
|
||||||
|
output += '\n var buf = textEncoder.encode(str)'
|
||||||
|
output += '\n ' + treeIntoCompiledTreeBufferBranch(paths, tree, 1, [], 3)
|
||||||
|
output += '\n return null'
|
||||||
|
output += '\n}'
|
||||||
|
if (Debug) {
|
||||||
|
console.log(output)
|
||||||
|
}
|
||||||
|
return new Function(output)()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compilePaths(paths) {
|
||||||
|
let splitPaths = splitAndSortPaths(paths)
|
||||||
|
let tree = buildTree(splitPaths.paramsPaths.slice())
|
||||||
|
printTree(tree, 0)
|
||||||
|
let compiled = treeIntoCompiledTreeBuffer(paths, tree)
|
||||||
|
let compiledString = treeIntoCompiledTreeString(paths, tree)
|
||||||
|
let compiledStringIndices = treeIntoCompiledTreeStringIndices(paths, tree)
|
||||||
|
|
||||||
|
let compiledTextOne = treeIntoCompiledTreeBufferAltOne(paths, tree)
|
||||||
|
|
||||||
|
// let compiledTextTwo = treeIntoCompiledTreeBufferAltTwo(paths, tree)
|
||||||
|
// let compiledTextThree = treeIntoCompiledTreeBufferAltThree(paths, tree)
|
||||||
|
return [
|
||||||
|
[compiled, compiled.bind(null, paths, splitPaths.staticPaths)],
|
||||||
|
[compiledTextOne, compiledTextOne.bind(null, paths, splitPaths.staticPaths)],
|
||||||
|
// [compiledTextTwo, compiledTextTwo.bind(null, new TextEncoder(), new TextDecoder(), paths, splitPaths.staticPaths)],
|
||||||
|
// [compiledTextThree, compiledTextThree.bind(null, new TextEncoder(), paths, splitPaths.staticPaths)],
|
||||||
|
[compiledString, compiledString.bind(null, paths, splitPaths.staticPaths)],
|
||||||
|
[compiledStringIndices, compiledStringIndices.bind(null, paths, splitPaths.staticPaths)],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compilePathsBuffer(paths) {
|
||||||
|
let splitPaths = splitAndSortPaths(paths)
|
||||||
|
let tree = buildTree(splitPaths.paramsPaths.slice())
|
||||||
|
printTree(tree, 0)
|
||||||
|
let compiled = treeIntoCompiledTreeBuffer(paths, tree)
|
||||||
|
let compiledString = treeIntoCompiledTreeString(paths, tree)
|
||||||
|
let compiledStringIndices = treeIntoCompiledTreeStringIndices(paths, tree)
|
||||||
|
|
||||||
|
let compiledTextOne = treeIntoCompiledTreeBufferAltOne(paths, tree)
|
||||||
|
|
||||||
|
// let compiledTextTwo = treeIntoCompiledTreeBufferAltTwo(paths, tree)
|
||||||
|
// let compiledTextThree = treeIntoCompiledTreeBufferAltThree(paths, tree)
|
||||||
|
return [
|
||||||
|
[compiled, compiled.bind(null, paths, splitPaths.staticPaths)],
|
||||||
|
[compiledTextOne, compiledTextOne.bind(null, paths, splitPaths.staticPaths)],
|
||||||
|
// [compiledTextTwo, compiledTextTwo.bind(null, new TextEncoder(), new TextDecoder(), paths, splitPaths.staticPaths)],
|
||||||
|
// [compiledTextThree, compiledTextThree.bind(null, new TextEncoder(), paths, splitPaths.staticPaths)],
|
||||||
|
[compiledString, compiledString.bind(null, paths, splitPaths.staticPaths)],
|
||||||
|
[compiledStringIndices, compiledStringIndices.bind(null, paths, splitPaths.staticPaths)],
|
||||||
|
]
|
||||||
|
}
|
16
benchmark/router_v2_compile.mjs
Normal file
16
benchmark/router_v2_compile.mjs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { compilePaths } from "../router_v2.mjs"
|
||||||
|
import { printTime } from './utils.mjs'
|
||||||
|
import * as consts from './const.js'
|
||||||
|
|
||||||
|
let paths = consts.allManyRoutes.map(x => ({ path: x }))
|
||||||
|
|
||||||
|
let s1 = process.hrtime.bigint()
|
||||||
|
let s2 = process.hrtime.bigint()
|
||||||
|
|
||||||
|
compilePaths(paths)
|
||||||
|
|
||||||
|
let s3 = process.hrtime.bigint()
|
||||||
|
|
||||||
|
let time = s3 - s2 - (s2 - s1)
|
||||||
|
|
||||||
|
printTime(time)
|
117
benchmark/set_arr.mjs
Normal file
117
benchmark/set_arr.mjs
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
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();
|
80
benchmark/string.js
Normal file
80
benchmark/string.js
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import { summary, run, bench } from 'mitata';
|
||||||
|
|
||||||
|
// Warmup (de-optimize `bench()` calls)
|
||||||
|
bench('noop', () => { });
|
||||||
|
bench('noop2', () => { });
|
||||||
|
|
||||||
|
// Example benchmark
|
||||||
|
summary(() => {
|
||||||
|
const dataset = new Array(100).fill(0).map(
|
||||||
|
() => new Array(10).fill(0).map(() => String.fromCharCode(97 + Math.round(Math.random() * 22))).join('')
|
||||||
|
);
|
||||||
|
console.log(dataset)
|
||||||
|
|
||||||
|
const fn1 = (str) => (str[0] === 'c' ? 1 : 0) + (str[1] === 'c' ? 1 : 0) + (str[2] === 'c' ? 1 : 0);
|
||||||
|
fn1('a');
|
||||||
|
fn1('b');
|
||||||
|
fn1('c');
|
||||||
|
fn1('d');
|
||||||
|
// optimizeNextInvocation(fn1);
|
||||||
|
bench('Char check', () => dataset.map(fn1))
|
||||||
|
|
||||||
|
const fn2 = (str) => (str.charCodeAt(0) === 99 ? 1 : 0) + (str.charCodeAt(1) === 99 ? 1 : 0) + (str.charCodeAt(2) === 99 ? 1 : 0);
|
||||||
|
fn2('a');
|
||||||
|
fn2('b');
|
||||||
|
fn2('c');
|
||||||
|
fn2('d');
|
||||||
|
// optimizeNextInvocation(fn2);
|
||||||
|
bench('Char code check', () => dataset.map(fn2));
|
||||||
|
|
||||||
|
|
||||||
|
bench('2x Char check', () => dataset.map(fn1))
|
||||||
|
bench('2x Char code check', () => dataset.map(fn2));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Example benchmark
|
||||||
|
summary(() => {
|
||||||
|
let paths = [
|
||||||
|
'test1/',
|
||||||
|
'test/',
|
||||||
|
'test3/',
|
||||||
|
'test/',
|
||||||
|
'test5/',
|
||||||
|
'something/',
|
||||||
|
'else/',
|
||||||
|
'goes/',
|
||||||
|
'here/',
|
||||||
|
'too/',
|
||||||
|
]
|
||||||
|
|
||||||
|
function fastCheck(str) {
|
||||||
|
if (str.charCodeAt(0) === 116) {
|
||||||
|
if (str.charCodeAt(1) === 101) {
|
||||||
|
if (str.charCodeAt(2) === 115) {
|
||||||
|
if (str.charCodeAt(3) === 116) {
|
||||||
|
if (str.charCodeAt(4) === 47) {
|
||||||
|
if (str.indexOf('/', 5) === -1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let r = new RegExp('^test/$')
|
||||||
|
function regexCheck(str) {
|
||||||
|
return r.test(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(paths.map(fastCheck))
|
||||||
|
console.log(paths.map(regexCheck))
|
||||||
|
|
||||||
|
bench('fastCheck', () => paths.map(fastCheck))
|
||||||
|
bench('regexCheck', () => paths.map(regexCheck));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
run();
|
121
benchmark/strings.mjs
Normal file
121
benchmark/strings.mjs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
import { buildTree, compileTreeIntoIfs, compileTreeIntoIfsWithBuffer } from "./compiler/compiler.mjs"
|
||||||
|
import { summary, run, bench } from 'mitata';
|
||||||
|
import { printCurrentStatus, printStatusHelperText } from "./compiler/utils.mjs";
|
||||||
|
import { FlaskaRouter } from "../flaska.mjs";
|
||||||
|
|
||||||
|
// Warmup (de-optimize `bench()` calls)
|
||||||
|
bench('noop', () => { });
|
||||||
|
bench('noop2', () => { });
|
||||||
|
|
||||||
|
let paths = [
|
||||||
|
'/fsdafasfa',
|
||||||
|
'/ymreklhmse',
|
||||||
|
'/h34nmlaeknmgl',
|
||||||
|
'/asdgsdagas',
|
||||||
|
'/ahaewhweaaa',
|
||||||
|
'/adshashaea',
|
||||||
|
'/sdafasfsadfasdfas',
|
||||||
|
//'/gdfsfgsfdsgsdrgsregsergsregersgserersgsergersg',
|
||||||
|
//'/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk',
|
||||||
|
]
|
||||||
|
let pathsBla = [
|
||||||
|
'/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk',
|
||||||
|
'/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk',
|
||||||
|
'/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk',
|
||||||
|
'/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk',
|
||||||
|
'/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk',
|
||||||
|
'/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk',
|
||||||
|
'/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk',
|
||||||
|
'/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk',
|
||||||
|
'/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk',
|
||||||
|
'/test5afdlkasdflksad flsakdf lsakf asdlkfa lsdkfasdlkdfalklk',
|
||||||
|
]
|
||||||
|
let pathsBuffer = paths.map(x => new Uint8Array(Buffer.from(x)))
|
||||||
|
|
||||||
|
let tree = buildTree(paths)
|
||||||
|
let noop = function() { }
|
||||||
|
|
||||||
|
const ifTree = compileTreeIntoIfs(tree)
|
||||||
|
// console.log(ifTree.toString())
|
||||||
|
const ifTreeBuffer = compileTreeIntoIfsWithBuffer(tree)
|
||||||
|
// console.log(ifTreeBuffer.toString())
|
||||||
|
const flaskaRouter = new FlaskaRouter()
|
||||||
|
for (let path of paths) {
|
||||||
|
flaskaRouter.addRoute(path, noop)
|
||||||
|
}
|
||||||
|
|
||||||
|
const m = new Map(paths.map(x => [x, x]))
|
||||||
|
function mapFlat(str) {
|
||||||
|
return m.get(str) ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
function toBuffer(str) {
|
||||||
|
return Buffer.from(str)
|
||||||
|
}
|
||||||
|
function toUint(str) {
|
||||||
|
return new Uint8Array(Buffer.from(str))
|
||||||
|
}
|
||||||
|
function toManualArray(str) {
|
||||||
|
let length = str.length
|
||||||
|
let out = new Array(length)
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
out[i] = str.charCodeAt(i)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
function allocBufferUnsafe(str) {
|
||||||
|
return Buffer.allocUnsafe(str.length)
|
||||||
|
}
|
||||||
|
function allocBufferSafe(str) {
|
||||||
|
return Buffer.alloc(str.length)
|
||||||
|
}
|
||||||
|
function allocUint8(str) {
|
||||||
|
return new Uint8Array(str.length)
|
||||||
|
}
|
||||||
|
function toManualArraySplitMap(str) {
|
||||||
|
return str.split('').map(x => x.charCodeAt(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
let func1 = [mapFlat, toBuffer, toUint, toManualArray, toManualArraySplitMap, allocBufferUnsafe, allocBufferSafe, allocUint8, ifTree];
|
||||||
|
for (var i = 0; i < 100000; i++) {
|
||||||
|
for (let fun of func1) {
|
||||||
|
paths.map(fun)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (var i = 0; i < 100000; i++) {
|
||||||
|
for (let path of paths) {
|
||||||
|
flaskaRouter.match(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let fun of func1) {
|
||||||
|
printCurrentStatus(fun);
|
||||||
|
}
|
||||||
|
printCurrentStatus(flaskaRouter.match)
|
||||||
|
printStatusHelperText()
|
||||||
|
|
||||||
|
summary(() => {
|
||||||
|
bench('if tree', function() {
|
||||||
|
return paths.map(ifTree)
|
||||||
|
})
|
||||||
|
bench('if tree buffer edition', function() {
|
||||||
|
return paths.map(ifTreeBuffer)
|
||||||
|
});
|
||||||
|
bench('flaskarouter', function() {
|
||||||
|
return paths.map(flaskaRouter.match.bind(flaskaRouter))
|
||||||
|
});
|
||||||
|
/*bench('map edition', function() {
|
||||||
|
return paths.map(mapFlat)
|
||||||
|
});
|
||||||
|
bench('if tree pre buffer edition', function() {
|
||||||
|
return pathsBuffer.map(ifTreeBuffer)
|
||||||
|
});
|
||||||
|
bench('toBuffer', () => pathsBla.map(toBuffer));
|
||||||
|
bench('toUint', () => pathsBla.map(toUint));
|
||||||
|
bench('toManualArraySplitMap', () => pathsBla.map(toManualArraySplitMap))
|
||||||
|
bench('toManualArray', () => pathsBla.map(toManualArray))*/
|
||||||
|
bench('allocBufferUnsafe', () => pathsBla.map(allocBufferUnsafe))
|
||||||
|
bench('allocBufferSafe', () => pathsBla.map(allocBufferSafe))
|
||||||
|
bench('allocUint8', () => pathsBla.map(allocUint8))
|
||||||
|
})
|
||||||
|
|
||||||
|
run();
|
115
benchmark/strings_compare.mjs
Normal file
115
benchmark/strings_compare.mjs
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
import { printCurrentStatus, printStatusHelperText } from "./compiler/utils.mjs";
|
||||||
|
import { summary, run, bench } from 'mitata';
|
||||||
|
|
||||||
|
// Do warmup
|
||||||
|
bench('noop', () => { })
|
||||||
|
bench('noop2', () => { })
|
||||||
|
|
||||||
|
// Do checks with arrays
|
||||||
|
let arr = ['hi', 'there', 'nagna', 'randomlongstring', 'small']
|
||||||
|
arr = arr.map(x => [x, { path: x }])
|
||||||
|
let paths = arr.map(x => x[1])
|
||||||
|
|
||||||
|
// Do checks with objects
|
||||||
|
let obj = {};
|
||||||
|
let map = new Map()
|
||||||
|
for (let i = 0, l = arr.length; i < l; ++i) {
|
||||||
|
obj[arr[i][0]] = arr[i][1]
|
||||||
|
map.set(arr[i][0], arr[i][1])
|
||||||
|
};
|
||||||
|
let objHas1 = (obj, item) => { let x = obj[item]; if (x) { return x } return null };
|
||||||
|
let objHas2 = (obj, item) => { if (item in obj) { return obj[item] } return null }
|
||||||
|
let mapGet = (map, item) => { let x = map.get(item); if (x) { return x }; return null };
|
||||||
|
|
||||||
|
|
||||||
|
// Generate a function which compares the input string to all strings in the list
|
||||||
|
let basicIfChain = Function(`
|
||||||
|
return (paths, str) => {
|
||||||
|
${arr.map((item, i) => 'if (str === "' + item[0] + '") { return paths[' + i + '] }').join('\n ')}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
`)();
|
||||||
|
let basicIfChainAlt = Function(`
|
||||||
|
return (str) => {
|
||||||
|
${arr.map((item, i) => 'if (str === "' + item[0] + '") { return "' + item[0] + '" }').join('\n ')}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
`)();
|
||||||
|
function basicIfChainNoFunc(str) {
|
||||||
|
if (str === "hi") { return "hi" }
|
||||||
|
if (str === "there") { return "there" }
|
||||||
|
if (str === "nagna") { return "nagna" }
|
||||||
|
if (str === "randomlongstring") { return "randomlongstring" }
|
||||||
|
if (str === "small") { return "small" }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
function addTwo(a, b) {
|
||||||
|
return a + b
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`
|
||||||
|
return (str) => {
|
||||||
|
${arr.map((item, i) => 'if (str === "' + item[0] + '") { return "' + item[0] + '" }').join('\n ')}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
let func = [
|
||||||
|
['basicIfChainNoFunc', basicIfChainNoFunc, basicIfChainNoFunc],
|
||||||
|
['basicIfChainAlt', basicIfChainAlt, basicIfChainAlt],
|
||||||
|
['objHas1', objHas1, objHas1.bind(null, obj)],
|
||||||
|
['objHas2', objHas2, objHas2.bind(null, obj)],
|
||||||
|
['mapGet', mapGet, mapGet.bind(null, map)],
|
||||||
|
['basicIfChain', basicIfChain, basicIfChain.bind(null, paths)],
|
||||||
|
]
|
||||||
|
|
||||||
|
for (let [name, __, fun] of func) {
|
||||||
|
console.log(`--- Sanity checking ${name} ---`)
|
||||||
|
for (let a of arr) {
|
||||||
|
console.log(a[0], fun(a[0]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate strings
|
||||||
|
let newArr = [];
|
||||||
|
for (let i = 0; i < 100000; i++)
|
||||||
|
newArr.push(Math.random() < 0.5 ? `${Math.random()}` : arr[Math.round(Math.random() * 4)].slice(0, -1 >>> 0));
|
||||||
|
|
||||||
|
console.log(`--- warming up addTwo ---`)
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
console.log(`--- run ${i} ---`)
|
||||||
|
for (let num of newArr) {
|
||||||
|
addTwo(num.length, num.length + 1)
|
||||||
|
}
|
||||||
|
await new Promise(res => setTimeout(res, 1000))
|
||||||
|
printCurrentStatus(addTwo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (let [name, org, fun] of func) {
|
||||||
|
console.log(`--- warming up ${name} ---`)
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
console.log(`--- run ${i} ---`)
|
||||||
|
newArr.forEach(fun)
|
||||||
|
await new Promise(res => setTimeout(res, 1000))
|
||||||
|
printCurrentStatus(org);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('--- sleeping ---')
|
||||||
|
await new Promise(res => setTimeout(res, 1000))
|
||||||
|
|
||||||
|
|
||||||
|
for (let [_, org] of func) {
|
||||||
|
printCurrentStatus(org);
|
||||||
|
}
|
||||||
|
printStatusHelperText()
|
||||||
|
|
||||||
|
|
||||||
|
summary(() => {
|
||||||
|
func.forEach(([name, _, fun]) => {
|
||||||
|
bench(name, () => newArr.map(fun));
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
run();
|
44
benchmark/strings_v2.mjs
Normal file
44
benchmark/strings_v2.mjs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import { summary, run, bench } from 'mitata';
|
||||||
|
import { FlaskaRouter } from "../flaska.mjs";
|
||||||
|
|
||||||
|
// Warmup (de-optimize `bench()` calls)
|
||||||
|
bench('noop', () => { });
|
||||||
|
bench('noop2', () => { });
|
||||||
|
|
||||||
|
function padStart(length) {
|
||||||
|
return ''.padStart(length * 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
const spaces = ''.padStart(256)
|
||||||
|
|
||||||
|
function strSlice(length) {
|
||||||
|
return spaces.slice(0, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
function strSubstring(length) {
|
||||||
|
return spaces.substring(0, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
const testData = new Array(100000)
|
||||||
|
for (let i = 0; i < testData.length; i++) {
|
||||||
|
testData[i] = Math.round(Math.random() * 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
let func = [padStart, strSlice, strSubstring];
|
||||||
|
for (let fun of func) {
|
||||||
|
console.log(`--- warming up ${fun.name || 'mapl'} ---`)
|
||||||
|
for (var i = 0; i < 100; i++) {
|
||||||
|
testData.map(fun)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
summary(() => {
|
||||||
|
func.forEach(function(fun) {
|
||||||
|
// console.log(tests, fun, tests.map(fun))
|
||||||
|
bench(fun.name, function() {
|
||||||
|
return testData.map(fun)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
run();
|
33
benchmark/test.mjs
Normal file
33
benchmark/test.mjs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import assert from 'assert'
|
||||||
|
import { compilePaths } from "./router_v2.mjs"
|
||||||
|
import * as consts from './const.js'
|
||||||
|
|
||||||
|
let paths = [
|
||||||
|
{ path: '/aa/aa', },
|
||||||
|
{ path: '/aa/:blabla', },
|
||||||
|
{ path: '/::rest', },
|
||||||
|
]
|
||||||
|
|
||||||
|
// paths = consts.allManyRoutes.map(x => ({ path: x }))
|
||||||
|
|
||||||
|
let tests = [
|
||||||
|
['/', paths[5]],
|
||||||
|
['/aa', paths[5]],
|
||||||
|
['/aa/aa', paths[0]],
|
||||||
|
['/aa/_', paths[1]],
|
||||||
|
['/aa/_/aa', paths[2]],
|
||||||
|
['/aa/_/ab', paths[3]],
|
||||||
|
['/aa/_/bb', paths[4]],
|
||||||
|
]
|
||||||
|
|
||||||
|
tests = paths.map(p => ([p.path.replace(/:[^/]+/g, '_'), p]))
|
||||||
|
|
||||||
|
let func = compilePaths(paths)
|
||||||
|
for (let [_, fun] of func) {
|
||||||
|
console.log(`--- ${fun.name} ---`)
|
||||||
|
for (let test of tests) {
|
||||||
|
let check = fun(test[0])
|
||||||
|
console.log(test[0], check)
|
||||||
|
assert.strictEqual(check.path, test[1])
|
||||||
|
}
|
||||||
|
}
|
15
benchmark/utils.mjs
Normal file
15
benchmark/utils.mjs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
export function printTime (t) {
|
||||||
|
let time = Number(t)
|
||||||
|
let units = ['n', 'μ', 'm', 'c', 's']
|
||||||
|
let unit = units[0]
|
||||||
|
let unitPower = 1
|
||||||
|
for (let i = 0; i < units.length; i++) {
|
||||||
|
let power = Math.pow(10, (i + 1) * 3)
|
||||||
|
if (power * 2 > time) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
unitPower = power
|
||||||
|
unit = units[i]
|
||||||
|
}
|
||||||
|
console.log(t, '=', Number((time / unitPower).toFixed(2)), unit)
|
||||||
|
}
|
579
flaska.mjs
579
flaska.mjs
|
@ -37,16 +37,6 @@ export const MimeTypeDb = getDb()
|
||||||
* Router
|
* Router
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class Branch {
|
|
||||||
constructor() {
|
|
||||||
this.children = new Map()
|
|
||||||
this.paramName = null
|
|
||||||
this.fullparamName = null
|
|
||||||
this.handler = null
|
|
||||||
this.middlewares = []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ErrorCodes = {
|
export const ErrorCodes = {
|
||||||
ERR_CONNECTION_ABORTED: 'ERR_CON_ABORTED'
|
ERR_CONNECTION_ABORTED: 'ERR_CON_ABORTED'
|
||||||
}
|
}
|
||||||
|
@ -73,8 +63,6 @@ const statuses = {
|
||||||
304: true
|
304: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const __paramMapName = '__param'
|
|
||||||
const __fullParamMapName = '__fullparam'
|
|
||||||
|
|
||||||
function assertIsHandler(handler, name) {
|
function assertIsHandler(handler, name) {
|
||||||
if (typeof(handler) !== 'function') {
|
if (typeof(handler) !== 'function') {
|
||||||
|
@ -139,6 +127,7 @@ export function CorsHandler(opts = {}) {
|
||||||
exposeHeaders: opts.exposeHeaders || '',
|
exposeHeaders: opts.exposeHeaders || '',
|
||||||
maxAge: opts.maxAge || '',
|
maxAge: opts.maxAge || '',
|
||||||
}
|
}
|
||||||
|
const allowAll = options.allowedOrigins.includes('*')
|
||||||
|
|
||||||
return function(ctx) {
|
return function(ctx) {
|
||||||
// Always add vary header on origin. Prevent caches from
|
// Always add vary header on origin. Prevent caches from
|
||||||
|
@ -154,7 +143,7 @@ export function CorsHandler(opts = {}) {
|
||||||
// Check origin is specified. Nothing needs to be done if
|
// Check origin is specified. Nothing needs to be done if
|
||||||
// there is no origin or it doesn't match
|
// there is no origin or it doesn't match
|
||||||
let origin = ctx.req.headers['origin']
|
let origin = ctx.req.headers['origin']
|
||||||
if (!origin || !options.allowedOrigins.includes(origin)) {
|
if (!origin || (!allowAll && !options.allowedOrigins.includes(origin))) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,7 +226,7 @@ export function FormidableHandler(formidable, org = {}) {
|
||||||
|
|
||||||
return new Promise(function(res, rej) {
|
return new Promise(function(res, rej) {
|
||||||
form.parse(ctx.req, function(err, fields, files) {
|
form.parse(ctx.req, function(err, fields, files) {
|
||||||
if (err) return rej(err)
|
if (err) return rej(new HttpError(400, err.message))
|
||||||
|
|
||||||
if (opts.parseFields) {
|
if (opts.parseFields) {
|
||||||
Object.keys(fields).forEach(function(key) {
|
Object.keys(fields).forEach(function(key) {
|
||||||
|
@ -248,29 +237,45 @@ export function FormidableHandler(formidable, org = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.req.body = fields
|
ctx.req.body = fields
|
||||||
ctx.req.file = files?.file || null
|
ctx.req.files = files
|
||||||
|
ctx.req.file = null
|
||||||
|
|
||||||
if (!ctx.req.file) {
|
|
||||||
|
if (!ctx.req.files) {
|
||||||
return res()
|
return res()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let keys = Object.keys(files).filter(key => Boolean(ctx.req.files[key]))
|
||||||
|
|
||||||
|
Promise.all(
|
||||||
|
keys.map(key => {
|
||||||
let filename
|
let filename
|
||||||
let target
|
let target
|
||||||
|
|
||||||
try {
|
try {
|
||||||
filename = opts.filename(ctx.req.file) || ctx.req.file.name
|
filename = opts.filename(ctx.req.files[key]) || ctx.req.files[key].name
|
||||||
target = path.join(opts.uploadDir, filename)
|
target = path.join(opts.uploadDir, filename)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return rej(err)
|
return Promise.reject(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rename(ctx.req.file.path, target)
|
return rename(ctx.req.files[key].path, target)
|
||||||
.then(function() {
|
.then(function() {
|
||||||
ctx.req.file.path = target
|
if (!ctx.req.files[key].type || ctx.req.files[key].type === 'application/octet-stream') {
|
||||||
ctx.req.file.filename = filename
|
let found = MimeTypeDb[path.extname(filename).slice(1)]
|
||||||
|
ctx.req.files[key].type = found && found[0] || 'application/octet-stream'
|
||||||
|
}
|
||||||
|
ctx.req.files[key].path = target
|
||||||
|
ctx.req.files[key].filename = filename
|
||||||
})
|
})
|
||||||
.then(res, rej)
|
})
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
if (keys.length === 1 && keys[0] === 'file') {
|
||||||
|
ctx.req.file = ctx.req.files.file
|
||||||
|
}
|
||||||
|
res()
|
||||||
|
}, rej)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -386,190 +391,322 @@ 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 spaces = ' '
|
||||||
|
|
||||||
export class FlaskaRouter {
|
export class FlaskaRouter {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.root = new Branch()
|
this.paths = []
|
||||||
|
this.registeredPaths = new Set()
|
||||||
}
|
}
|
||||||
|
|
||||||
addRoute(route, orgMiddlewares, orgHandler) {
|
addRoute(path, middlewares, orgHandler) {
|
||||||
if (route[0] !== '/')
|
if (path[0] !== '/')
|
||||||
throw new Error(`route "${route}" must start with forward slash`)
|
throw new RouterError(null, null, `addRoute("${path}") path must start with forward slash`)
|
||||||
|
|
||||||
let middlewares = orgMiddlewares
|
let cleaned = path
|
||||||
let handler = orgHandler
|
if (cleaned.indexOf('/:') >= 0) {
|
||||||
if (!orgHandler) {
|
cleaned = cleaned.replace(regStarDoubleParam, '**').replace(regStarSingleParam, '*')
|
||||||
handler = orgMiddlewares
|
if (cleaned.indexOf(':') > 0) {
|
||||||
middlewares = []
|
throw new RouterError(null, null, `addRoute("${path}") path has missing name or word between two forward slashes`)
|
||||||
}
|
}
|
||||||
if (middlewares && typeof(middlewares) === 'function') {
|
if (cleaned.indexOf('**/') > 0) {
|
||||||
middlewares = [middlewares]
|
throw new RouterError(null, null, `addRoute("${path}") cannot add anything after a full param route`)
|
||||||
}
|
}
|
||||||
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) {
|
|
||||||
let name = route
|
|
||||||
if (name.length > 1 && name[name.length - 1] === '/') {
|
|
||||||
name = name.slice(0, -1)
|
|
||||||
}
|
}
|
||||||
let child = new Branch()
|
if (cleaned.indexOf('//') >= 0) {
|
||||||
branch.children.set(name, child)
|
throw new RouterError(null, null, `addRoute("${path}") path has missing name or word between two forward slashes`)
|
||||||
child.handler = handler
|
|
||||||
child.middlewares = middlewares
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 1; i <= route.length; i++) {
|
let size = this.registeredPaths.size
|
||||||
if ((i === route.length || route[i] === '/') && end > start) {
|
if (this.registeredPaths.add(cleaned).size === size) {
|
||||||
if (branch.fullparamName) {
|
throw new RouterError(null, null, `addRoute("${path}") found an existing route with same path of ${cleaned}`)
|
||||||
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}`)
|
|
||||||
}
|
|
||||||
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`)
|
|
||||||
}
|
|
||||||
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++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match(orgUrl) {
|
let handlers = []
|
||||||
let url = orgUrl
|
if (Array.isArray(middlewares)) {
|
||||||
if (url.length > 1 && url[url.length - 1] === '/') {
|
handlers.push(...middlewares)
|
||||||
url = url.slice(0, -1)
|
if (typeof(orgHandler) !== 'function') {
|
||||||
}
|
throw new RouterError(orgHandler, null, `addRoute("${path}") was called with a handler that was not a function`)
|
||||||
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 {
|
} else {
|
||||||
if (output = this.root.children.get(__fullParamMapName)) {
|
handlers.push(middlewares)
|
||||||
params = {
|
|
||||||
[output.fullparamName]: url.slice(1)
|
|
||||||
}
|
}
|
||||||
return {
|
if (orgHandler) {
|
||||||
handler: output.handler,
|
handlers.push(orgHandler)
|
||||||
middlewares: output.middlewares,
|
}
|
||||||
params: params,
|
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`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
|
this.paths.push({
|
||||||
|
path,
|
||||||
|
handlers
|
||||||
|
})
|
||||||
}
|
}
|
||||||
i++
|
|
||||||
end = start = i
|
__buildChild(x, i, splitPaths) {
|
||||||
char = url[i]
|
let splitPath = splitPaths[0]
|
||||||
}
|
let letter = new Child(splitPath.split, x, i)
|
||||||
// Check branch.handler. This can happen if route /::path is added
|
|
||||||
// and request is '/' it will attempt to match root which will fail
|
let consume = []
|
||||||
if (i >= url.length && branch.handler) {
|
if (splitPath.split.length === x + 1
|
||||||
return {
|
&& (splitPath.split[x].isParams
|
||||||
handler: branch.handler,
|
|| splitPath.split[x].isFullParams
|
||||||
middlewares: branch.middlewares,
|
|| splitPath.split[x].word.length === i + 1)) {
|
||||||
params: params,
|
letter.path = splitPath.entry
|
||||||
}
|
letter.count += 1
|
||||||
}
|
|
||||||
if (char === '/') {
|
|
||||||
end = start = i + 1
|
|
||||||
} else {
|
} else {
|
||||||
end++
|
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)
|
||||||
}
|
}
|
||||||
if (output = this.root.children.get(__fullParamMapName)) {
|
|
||||||
params = {
|
letter.count += consume.length
|
||||||
[output.fullparamName]: url.slice(1)
|
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' });
|
||||||
|
let sealed = Object.seal({})
|
||||||
|
|
||||||
|
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: sealed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect params path separately
|
||||||
|
paramsPaths.push({
|
||||||
|
split: entry.path.slice(1).split(/\//g).map(function(word) {
|
||||||
|
let actualWord = word.replace(regParamPrefix, '')
|
||||||
return {
|
return {
|
||||||
handler: output.handler,
|
word: actualWord,
|
||||||
middlewares: output.middlewares,
|
isParams: word[0] === ':' && word[1] !== ':',
|
||||||
params: params,
|
isFullParams: word[0] === ':' && word[1] === ':',
|
||||||
|
paramVarName: word[0] === ':'
|
||||||
|
? actualWord.replace(regCleanNonAschii, '_').replace(regCleanRest, '_')
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
throw new RouterError(aGroup, bGroup, 'Two identical paths were found')
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
staticPaths,
|
||||||
|
paramsPaths,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
|
|
||||||
|
__getIndex(offset, additions, params) {
|
||||||
|
return (offset + additions)
|
||||||
|
+ (params.length
|
||||||
|
? ' + ' + params.map(a => `offset${a[1]}`).join(' + ')
|
||||||
|
: '')
|
||||||
|
}
|
||||||
|
|
||||||
|
__treeIntoCompiledCodeReturnPath(indentString, paths, branch, params, pathParamMapIndex) {
|
||||||
|
let pathIndex = paths.indexOf(branch.path)
|
||||||
|
if (pathIndex < 0) {
|
||||||
|
throw new RouterError(branch.path, null, 'InternalError: Specified path was not found in paths')
|
||||||
|
}
|
||||||
|
let mapIndex = pathParamMapIndex.size + 1
|
||||||
|
pathParamMapIndex.set(mapIndex, pathIndex)
|
||||||
|
let output = '\n' + indentString + `return {`
|
||||||
|
output += '\n' + indentString + ` path: paths_${mapIndex},`
|
||||||
|
if (params.length) {
|
||||||
|
output += '\n' + indentString + ` params: {`
|
||||||
|
for (let param of params) {
|
||||||
|
output += '\n' + indentString + ` ${param[0]}: s${param[1]},`
|
||||||
|
}
|
||||||
|
output += '\n' + indentString + ` },`
|
||||||
|
} else {
|
||||||
|
output += '\n' + indentString + ` {},`
|
||||||
|
}
|
||||||
|
output += '\n' + indentString + `}`
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
__treeIntoCompiledCodeBranch(paths, branches, indent = 0, params = [], pathParamMapIndex) {
|
||||||
|
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 '
|
||||||
|
} else {
|
||||||
|
// output += '} //'
|
||||||
|
output += '\n' + indentation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (strLength === ${this.__getIndex(indent, 1, params)}) {`
|
||||||
|
output += this.__treeIntoCompiledCodeReturnPath(indentation + ' ', paths, branch, params, pathParamMapIndex)
|
||||||
|
output += '\n' + indentation + ` }`
|
||||||
|
}
|
||||||
|
} 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, pathParamMapIndex)
|
||||||
|
} else if (branch.path) {
|
||||||
|
output += '\n' + indentation + `if (strLength === ${this.__getIndex(indent, 0, params)}) {`
|
||||||
|
output += this.__treeIntoCompiledCodeReturnPath(indentation + ' ', paths, branch, params, pathParamMapIndex)
|
||||||
|
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(), pathParamMapIndex)
|
||||||
|
}
|
||||||
|
if (addEndBracket) {
|
||||||
|
output += '\n' + indentation + '} '
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
__treeIntoCompiledCodeClosure(paths, tree, staticList) {
|
||||||
|
let pathParamMapIndex = new Map()
|
||||||
|
let output = ''
|
||||||
|
let prefix = ''
|
||||||
|
if (staticList.size > 0) {
|
||||||
|
output += '\n let checkStatic = staticList.get(str)'
|
||||||
|
output += '\n if(checkStatic) {'
|
||||||
|
output += '\n return checkStatic'
|
||||||
|
output += '\n }'
|
||||||
|
}
|
||||||
|
if (tree.length) {
|
||||||
|
output += '\n let strLength = str.length'
|
||||||
|
output += '\n ' + this.__treeIntoCompiledCodeBranch(paths, tree, 1, [], pathParamMapIndex)
|
||||||
|
|
||||||
|
}
|
||||||
|
output += '\n return null'
|
||||||
|
output += '\n}'
|
||||||
|
pathParamMapIndex.forEach(function (val, key) {
|
||||||
|
prefix += `let paths_${key} = paths[${val}]\n`
|
||||||
|
})
|
||||||
|
output = prefix + 'return function flaskaFastRouter(str) {\n "use strict";' + 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -626,7 +763,7 @@ export class Flaska {
|
||||||
defaultHeaders: opts.defaultHeaders || {
|
defaultHeaders: opts.defaultHeaders || {
|
||||||
'Server': 'Flaska',
|
'Server': 'Flaska',
|
||||||
'X-Content-Type-Options': 'nosniff',
|
'X-Content-Type-Options': 'nosniff',
|
||||||
'Content-Security-Policy': `default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data: blob:; object-src 'none'; frame-ancestors 'none'`,
|
'Content-Security-Policy': `default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data: blob:; font-src 'self' data:; object-src 'none'; frame-ancestors 'none'`,
|
||||||
'Cross-Origin-Opener-Policy': 'same-origin',
|
'Cross-Origin-Opener-Policy': 'same-origin',
|
||||||
'Cross-Origin-Resource-Policy': 'same-origin',
|
'Cross-Origin-Resource-Policy': 'same-origin',
|
||||||
'Cross-Origin-Embedder-Policy': 'require-corp',
|
'Cross-Origin-Embedder-Policy': 'require-corp',
|
||||||
|
@ -644,6 +781,13 @@ export class Flaska {
|
||||||
nonceCacheLength: opts.nonceCacheLength || 25
|
nonceCacheLength: opts.nonceCacheLength || 25
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts.appendHeaders) {
|
||||||
|
let appendKeys = Object.keys(opts.appendHeaders)
|
||||||
|
for (let key of appendKeys) {
|
||||||
|
options.defaultHeaders[key] = opts.appendHeaders[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!options.defaultHeaders && options.nonce.length) {
|
if (!options.defaultHeaders && options.nonce.length) {
|
||||||
// throw error
|
// throw error
|
||||||
}
|
}
|
||||||
|
@ -730,12 +874,6 @@ ctx.state.nonce = nonce;
|
||||||
this.patch = this.routers.PATCH.addRoute.bind(this.routers.PATCH)
|
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() {
|
devMode() {
|
||||||
this._backuperror = this._onerror = function(err, ctx) {
|
this._backuperror = this._onerror = function(err, ctx) {
|
||||||
ctx.log.error(err)
|
ctx.log.error(err)
|
||||||
|
@ -870,37 +1008,25 @@ ctx.state.nonce = nonce;
|
||||||
|
|
||||||
ctx.params = route.params
|
ctx.params = route.params
|
||||||
|
|
||||||
if (route.middlewares.length) {
|
let handlers = this.runHandlers(ctx, route.path.handlers, 0)
|
||||||
let middle = this.handleMiddleware(ctx, route.middlewares, 0)
|
|
||||||
|
|
||||||
if (middle && middle.then) {
|
if (handlers && handlers.then) {
|
||||||
return middle.then(() => {
|
return handlers.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)
|
this.requestEnd(null, ctx)
|
||||||
}, err => {
|
}, err => {
|
||||||
this.requestEnd(err, ctx)
|
this.requestEnd(err, ctx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.requestEnd(null, ctx)
|
this.requestEnd(null, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMiddleware(ctx, middles, index) {
|
runHandlers(ctx, middles, index) {
|
||||||
for (let i = index; i < middles.length; i++) {
|
for (let i = index; i < middles.length; i++) {
|
||||||
let res = middles[i](ctx)
|
let res = middles[i](ctx)
|
||||||
if (res && res.then) {
|
if (res && res.then) {
|
||||||
return res.then(() => {
|
return res.then(() => {
|
||||||
return this.handleMiddleware(ctx, middles, i + 1)
|
return this.runHandlers(ctx, middles, i + 1)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -928,7 +1054,7 @@ ctx.state.nonce = nonce;
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.body === null && !handleUsed && ctx.status === 200) {
|
if (ctx.body == null && !handleUsed && ctx.status === 200) {
|
||||||
ctx.status = 204
|
ctx.status = 204
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -966,7 +1092,10 @@ ctx.state.nonce = nonce;
|
||||||
|
|
||||||
let length = 0
|
let length = 0
|
||||||
|
|
||||||
if (typeof(body) === 'object' && body) {
|
if (body instanceof Buffer) {
|
||||||
|
length = body.byteLength
|
||||||
|
ctx.type = ctx.type || 'application/octet-stream'
|
||||||
|
} else if (typeof(body) === 'object' && body) {
|
||||||
body = JSON.stringify(body)
|
body = JSON.stringify(body)
|
||||||
length = Buffer.byteLength(body)
|
length = Buffer.byteLength(body)
|
||||||
ctx.type = 'application/json; charset=utf-8'
|
ctx.type = 'application/json; charset=utf-8'
|
||||||
|
@ -1033,6 +1162,28 @@ ctx.state.nonce = nonce;
|
||||||
this[`_${type}AsyncCompiled`] = func.bind(this, ...this[`_${type}Async`])
|
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() {
|
||||||
|
this.compile()
|
||||||
|
this.server = this.http.createServer(this.requestStart.bind(this))
|
||||||
|
|
||||||
|
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
|
||||||
|
socket.destroy();
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
listen(port, orgIp, orgcb) {
|
listen(port, orgIp, orgcb) {
|
||||||
|
@ -1045,8 +1196,8 @@ ctx.state.nonce = nonce;
|
||||||
if (typeof(port) !== 'number') {
|
if (typeof(port) !== 'number') {
|
||||||
throw new Error('Flaska.listen() called with non-number in port')
|
throw new Error('Flaska.listen() called with non-number in port')
|
||||||
}
|
}
|
||||||
this.compile()
|
|
||||||
this.server = this.http.createServer(this.requestStart.bind(this))
|
this.create()
|
||||||
|
|
||||||
this.server.listen(port, ip, cb)
|
this.server.listen(port, ip, cb)
|
||||||
}
|
}
|
||||||
|
@ -1056,8 +1207,7 @@ ctx.state.nonce = nonce;
|
||||||
return Promise.reject(new Error('Flaska.listen() called with non-number in port'))
|
return Promise.reject(new Error('Flaska.listen() called with non-number in port'))
|
||||||
}
|
}
|
||||||
|
|
||||||
this.compile()
|
this.create()
|
||||||
this.server = this.http.createServer(this.requestStart.bind(this))
|
|
||||||
|
|
||||||
if (this.server.listenAsync && typeof(this.server.listenAsync) === 'function') {
|
if (this.server.listenAsync && typeof(this.server.listenAsync) === 'function') {
|
||||||
return this.server.listenAsync(port, ip)
|
return this.server.listenAsync(port, ip)
|
||||||
|
@ -1079,8 +1229,9 @@ ctx.state.nonce = nonce;
|
||||||
if (err) { return rej(err) }
|
if (err) { return rej(err) }
|
||||||
|
|
||||||
// Waiting 0.1 second for it to close down
|
// Waiting 0.1 second for it to close down
|
||||||
setTimeout(function() { res() }, 100)
|
setTimeout(res, 100)
|
||||||
})
|
})
|
||||||
|
this.server.closeAllConnections()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
20
package.json
20
package.json
|
@ -1,21 +1,18 @@
|
||||||
{
|
{
|
||||||
"name": "flaska",
|
"name": "flaska",
|
||||||
"version": "1.2.3",
|
"version": "1.4.0",
|
||||||
"description": "Flaska is a micro web-framework for node. It is designed to be fast, simple and lightweight, and is distributed as a single file module with no dependencies.",
|
"description": "Flaska is a micro web-framework for node. It is designed to be fast, simple and lightweight, and is distributed as a single file module with no dependencies.",
|
||||||
"main": "flaska.mjs",
|
"main": "flaska.mjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "eltro",
|
"test": "eltro -r dot",
|
||||||
"test:watch": "npm-watch test"
|
"test:watch": "eltro -r dot -w test"
|
||||||
},
|
},
|
||||||
"watch": {
|
"watch": {
|
||||||
"test": {
|
"test": {
|
||||||
"patterns": [
|
"patterns": [
|
||||||
"test/*",
|
"./"
|
||||||
"flaska.mjs"
|
|
||||||
],
|
],
|
||||||
"extensions": "mjs",
|
"extensions": "mjs"
|
||||||
"quiet": true,
|
|
||||||
"inherit": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
@ -39,9 +36,14 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://git.nfp.is/TheThing/flaska/#readme",
|
"homepage": "https://git.nfp.is/TheThing/flaska/#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eltro": "^1.2.3",
|
"eltro": "^1.3.2",
|
||||||
"formidable": "^1.2.2"
|
"formidable": "^1.2.2"
|
||||||
},
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"allowedDeprecatedVersions": {
|
||||||
|
"formidable": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"flaska.mjs",
|
"flaska.mjs",
|
||||||
"README.md",
|
"README.md",
|
||||||
|
|
16
test.mjs
16
test.mjs
|
@ -1,16 +0,0 @@
|
||||||
import { Flaska } from './flaska.mjs'
|
|
||||||
|
|
||||||
const port = 51024
|
|
||||||
const flaska = new Flaska({}, )
|
|
||||||
|
|
||||||
flaska.get('/', function(ctx) {
|
|
||||||
ctx.body = { status: true }
|
|
||||||
})
|
|
||||||
|
|
||||||
flaska.get('/error', function(ctx) {
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
flaska.listen(port, function() {
|
|
||||||
console.log('listening on port', port)
|
|
||||||
})
|
|
|
@ -126,27 +126,47 @@ const random = (length = 8) => {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
Client.prototype.upload = function(url, file, method = 'POST', body = {}) {
|
Client.prototype.upload = function(url, files, method = 'POST', body = {}, overrideType = null) {
|
||||||
return fs.readFile(file).then(data => {
|
|
||||||
const crlf = '\r\n'
|
|
||||||
const filename = path.basename(file)
|
|
||||||
const boundary = `---------${random(32)}`
|
const boundary = `---------${random(32)}`
|
||||||
|
const crlf = '\r\n'
|
||||||
|
let upload = files
|
||||||
|
|
||||||
const multipartBody = Buffer.concat([
|
if (typeof(upload) === 'string') {
|
||||||
Buffer.from(
|
upload = {
|
||||||
`${crlf}--${boundary}${crlf}` +
|
file: files
|
||||||
`Content-Disposition: form-data; name="file"; filename="${filename}"` + crlf + crlf
|
}
|
||||||
),
|
}
|
||||||
data,
|
let keys = Object.keys(upload)
|
||||||
|
let uploadBody = []
|
||||||
|
|
||||||
|
return Promise.all(keys.map(key => {
|
||||||
|
let file = upload[key]
|
||||||
|
return fs.readFile(file).then(data => {
|
||||||
|
const filename = path.basename(file)
|
||||||
|
|
||||||
|
uploadBody.push(Buffer.from(
|
||||||
|
`${crlf}--${boundary}${crlf}`
|
||||||
|
+ `Content-Disposition: form-data; name="${key}"; filename="${filename}"`
|
||||||
|
+ (overrideType ? crlf + `Content-Type: ${overrideType}`: '')
|
||||||
|
+ crlf
|
||||||
|
+ crlf
|
||||||
|
))
|
||||||
|
uploadBody.push(data)
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
.then(() => {
|
||||||
|
uploadBody.push(
|
||||||
Buffer.concat(Object.keys(body).map(function(key) {
|
Buffer.concat(Object.keys(body).map(function(key) {
|
||||||
return Buffer.from(''
|
return Buffer.from(''
|
||||||
+ `${crlf}--${boundary}${crlf}`
|
+ `${crlf}--${boundary}${crlf}`
|
||||||
+ `Content-Disposition: form-data; name="${key}"` + crlf + crlf
|
+ `Content-Disposition: form-data; name="${key}"` + crlf + crlf
|
||||||
+ JSON.stringify(body[key])
|
+ JSON.stringify(body[key])
|
||||||
)
|
)
|
||||||
})),
|
}))
|
||||||
Buffer.from(`${crlf}--${boundary}--`),
|
)
|
||||||
])
|
uploadBody.push(Buffer.from(`${crlf}--${boundary}--`))
|
||||||
|
|
||||||
|
let multipartBody = Buffer.concat(uploadBody)
|
||||||
|
|
||||||
return this.customRequest(method, url, multipartBody, {
|
return this.customRequest(method, url, multipartBody, {
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
|
|
|
@ -50,7 +50,7 @@ t.describe('#constructor', function() {
|
||||||
|
|
||||||
assert.strictEqual(ctx.headers['Server'], 'Flaska')
|
assert.strictEqual(ctx.headers['Server'], 'Flaska')
|
||||||
assert.strictEqual(ctx.headers['X-Content-Type-Options'], 'nosniff')
|
assert.strictEqual(ctx.headers['X-Content-Type-Options'], 'nosniff')
|
||||||
assert.strictEqual(ctx.headers['Content-Security-Policy'], `default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data: blob:; object-src 'none'; frame-ancestors 'none'`)
|
assert.strictEqual(ctx.headers['Content-Security-Policy'], `default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data: blob:; font-src 'self' data:; object-src 'none'; frame-ancestors 'none'`)
|
||||||
assert.strictEqual(ctx.headers['Cross-Origin-Opener-Policy'], 'same-origin')
|
assert.strictEqual(ctx.headers['Cross-Origin-Opener-Policy'], 'same-origin')
|
||||||
assert.strictEqual(ctx.headers['Cross-Origin-Resource-Policy'], 'same-origin')
|
assert.strictEqual(ctx.headers['Cross-Origin-Resource-Policy'], 'same-origin')
|
||||||
assert.strictEqual(ctx.headers['Cross-Origin-Embedder-Policy'], 'require-corp')
|
assert.strictEqual(ctx.headers['Cross-Origin-Embedder-Policy'], 'require-corp')
|
||||||
|
@ -80,8 +80,7 @@ t.describe('#constructor', function() {
|
||||||
flaska._before[0](ctx)
|
flaska._before[0](ctx)
|
||||||
|
|
||||||
let keys = Object.keys(defaultHeaders)
|
let keys = Object.keys(defaultHeaders)
|
||||||
console.log(Object.keys(ctx.headers).sort())
|
|
||||||
console.log(keys.sort())
|
|
||||||
assert.strictEqual(Object.keys(ctx.headers).length, keys.length + 1)
|
assert.strictEqual(Object.keys(ctx.headers).length, keys.length + 1)
|
||||||
for (let key of keys) {
|
for (let key of keys) {
|
||||||
assert.strictEqual(ctx.headers[key], defaultHeaders[key])
|
assert.strictEqual(ctx.headers[key], defaultHeaders[key])
|
||||||
|
@ -90,6 +89,38 @@ t.describe('#constructor', function() {
|
||||||
|
|
||||||
assert.strictEqual(flaska._after.length, 0)
|
assert.strictEqual(flaska._after.length, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.test('should have before ready setting headers on context if appendHeaders is specified', function() {
|
||||||
|
const appendHeaders = {
|
||||||
|
'Server': 'nginx/1.16.1',
|
||||||
|
'Herp': 'Derp',
|
||||||
|
}
|
||||||
|
let flaska = new Flaska({
|
||||||
|
appendHeaders: appendHeaders,
|
||||||
|
}, faker)
|
||||||
|
assert.strictEqual(flaska._before.length, 1)
|
||||||
|
|
||||||
|
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()
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.notStrictEqual(ctx.headers['Server'], 'Flaska')
|
||||||
|
assert.strictEqual(ctx.headers['Server'], appendHeaders.Server)
|
||||||
|
assert.strictEqual(ctx.headers['Herp'], 'Derp')
|
||||||
|
assert.strictEqual(ctx.headers['X-Content-Type-Options'], 'nosniff')
|
||||||
|
assert.strictEqual(ctx.headers['Content-Security-Policy'], `default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data: blob:; font-src 'self' data:; object-src 'none'; frame-ancestors 'none'`)
|
||||||
|
assert.strictEqual(ctx.headers['Cross-Origin-Opener-Policy'], 'same-origin')
|
||||||
|
assert.strictEqual(ctx.headers['Cross-Origin-Resource-Policy'], 'same-origin')
|
||||||
|
assert.strictEqual(ctx.headers['Cross-Origin-Embedder-Policy'], 'require-corp')
|
||||||
|
assert.ok(new Date(ctx.headers['Date']).getDate())
|
||||||
|
|
||||||
|
assert.strictEqual(flaska._after.length, 0)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.describe('#_nonce', function() {
|
t.describe('#_nonce', function() {
|
||||||
|
@ -125,7 +156,7 @@ t.describe('#_nonce', function() {
|
||||||
|
|
||||||
assert.strictEqual(ctx.headers['Server'], 'Flaska')
|
assert.strictEqual(ctx.headers['Server'], 'Flaska')
|
||||||
assert.strictEqual(ctx.headers['X-Content-Type-Options'], 'nosniff')
|
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:; object-src 'none'; frame-ancestors 'none'; script-src 'nonce-${ctx.state.nonce}'`)
|
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}'`)
|
||||||
assert.strictEqual(ctx.headers['Cross-Origin-Opener-Policy'], 'same-origin')
|
assert.strictEqual(ctx.headers['Cross-Origin-Opener-Policy'], 'same-origin')
|
||||||
assert.strictEqual(ctx.headers['Cross-Origin-Resource-Policy'], 'same-origin')
|
assert.strictEqual(ctx.headers['Cross-Origin-Resource-Policy'], 'same-origin')
|
||||||
assert.strictEqual(ctx.headers['Cross-Origin-Embedder-Policy'], 'require-corp')
|
assert.strictEqual(ctx.headers['Cross-Origin-Embedder-Policy'], 'require-corp')
|
||||||
|
@ -143,7 +174,7 @@ t.describe('#_nonce', function() {
|
||||||
let nextNonce = flaska._nonces[flaska._noncesIndex]
|
let nextNonce = flaska._nonces[flaska._noncesIndex]
|
||||||
flaska._before[0](ctx)
|
flaska._before[0](ctx)
|
||||||
assert.strictEqual(ctx.state.nonce, nextNonce)
|
assert.strictEqual(ctx.state.nonce, nextNonce)
|
||||||
assert.strictEqual(ctx.headers['Content-Security-Policy'], `default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data: blob:; object-src 'none'; frame-ancestors 'none'; script-src 'nonce-${ctx.state.nonce}'`)
|
assert.strictEqual(ctx.headers['Content-Security-Policy'], `default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data: blob:; font-src 'self' data:; object-src 'none'; frame-ancestors 'none'; script-src 'nonce-${ctx.state.nonce}'`)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.notOk(flaska._nonces[flaska._noncesIndex])
|
assert.notOk(flaska._nonces[flaska._noncesIndex])
|
||||||
|
@ -157,7 +188,7 @@ t.describe('#_nonce', function() {
|
||||||
assert.notStrictEqual(ctx.state.nonce, flaska._nonces[i])
|
assert.notStrictEqual(ctx.state.nonce, flaska._nonces[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.strictEqual(ctx.headers['Content-Security-Policy'], `default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data: blob:; object-src 'none'; frame-ancestors 'none'; script-src 'nonce-${ctx.state.nonce}'`)
|
assert.strictEqual(ctx.headers['Content-Security-Policy'], `default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data: blob:; font-src 'self' data:; object-src 'none'; frame-ancestors 'none'; script-src 'nonce-${ctx.state.nonce}'`)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('should have after that regenerates lost hashes', function() {
|
t.test('should have after that regenerates lost hashes', function() {
|
||||||
|
@ -704,16 +735,16 @@ t.describe('#compile()', function() {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.describe('#handleMiddleware()', function() {
|
t.describe('#runHandlers()', function() {
|
||||||
t.test('should work with empty array', function() {
|
t.test('should work with empty array', function() {
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.handleMiddleware({}, [], 0)
|
flaska.runHandlers({}, [], 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('should work with correct index', function() {
|
t.test('should work with correct index', function() {
|
||||||
let checkIsTrue = false
|
let checkIsTrue = false
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.handleMiddleware({}, [
|
flaska.runHandlers({}, [
|
||||||
function() { throw new Error('should not be thrown') },
|
function() { throw new Error('should not be thrown') },
|
||||||
function() { throw new Error('should not be thrown') },
|
function() { throw new Error('should not be thrown') },
|
||||||
function() { throw new Error('should not be thrown') },
|
function() { throw new Error('should not be thrown') },
|
||||||
|
@ -726,7 +757,7 @@ t.describe('#handleMiddleware()', function() {
|
||||||
const assertCtx = createCtx({ a: 1 })
|
const assertCtx = createCtx({ a: 1 })
|
||||||
let checkCounter = 0
|
let checkCounter = 0
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.handleMiddleware(assertCtx, [
|
flaska.runHandlers(assertCtx, [
|
||||||
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
|
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
|
||||||
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
|
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
|
||||||
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
|
function(ctx) { assert.strictEqual(ctx, assertCtx); checkCounter++ },
|
||||||
|
@ -740,7 +771,7 @@ t.describe('#handleMiddleware()', function() {
|
||||||
const assertCtx = createCtx({ a: 1 })
|
const assertCtx = createCtx({ a: 1 })
|
||||||
let checkCounter = 0
|
let checkCounter = 0
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
let result = flaska.handleMiddleware(assertCtx, [
|
let result = flaska.runHandlers(assertCtx, [
|
||||||
function(ctx) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 0); checkCounter++ },
|
function(ctx) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 0); checkCounter++ },
|
||||||
function(ctx) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 1); 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() }) },
|
function(ctx) { return new Promise(function(res) { assert.strictEqual(ctx, assertCtx); assert.strictEqual(checkCounter, 2); checkCounter++; res() }) },
|
||||||
|
@ -762,19 +793,19 @@ t.describe('#handleMiddleware()', function() {
|
||||||
const assertError = { a: 1 }
|
const assertError = { a: 1 }
|
||||||
let checkCounter = 0
|
let checkCounter = 0
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
let err = await assert.isRejected(flaska.handleMiddleware({}, [
|
let err = await assert.isRejected(flaska.runHandlers({}, [
|
||||||
function() { },
|
function() { },
|
||||||
function() { return new Promise(function(res, rej) { rej(assertError) }) },
|
function() { return new Promise(function(res, rej) { rej(assertError) }) },
|
||||||
function() { throw new Error('should not be seen') },
|
function() { throw new Error('should not be seen') },
|
||||||
], 0))
|
], 0))
|
||||||
assert.strictEqual(err, assertError)
|
assert.strictEqual(err, assertError)
|
||||||
err = await assert.isRejected(flaska.handleMiddleware({}, [
|
err = await assert.isRejected(flaska.runHandlers({}, [
|
||||||
function() { },
|
function() { },
|
||||||
function() { return Promise.reject(assertError) },
|
function() { return Promise.reject(assertError) },
|
||||||
function() { throw new Error('should not be seen') },
|
function() { throw new Error('should not be seen') },
|
||||||
], 0))
|
], 0))
|
||||||
assert.strictEqual(err, assertError)
|
assert.strictEqual(err, assertError)
|
||||||
err = await assert.isRejected(flaska.handleMiddleware({}, [
|
err = await assert.isRejected(flaska.runHandlers({}, [
|
||||||
function() { },
|
function() { },
|
||||||
function() { return Promise.resolve() },
|
function() { return Promise.resolve() },
|
||||||
function() { throw assertError },
|
function() { throw assertError },
|
||||||
|
@ -914,6 +945,7 @@ t.describe('#listenAsync()', function() {
|
||||||
checkIp = ip
|
checkIp = ip
|
||||||
cb()
|
cb()
|
||||||
})
|
})
|
||||||
|
|
||||||
let flaska = new Flaska({}, testFaker)
|
let flaska = new Flaska({}, testFaker)
|
||||||
assert.ok(flaska.requestStart)
|
assert.ok(flaska.requestStart)
|
||||||
flaska.requestStart = function() {
|
flaska.requestStart = function() {
|
||||||
|
@ -956,6 +988,7 @@ t.describe('#listenAsync()', function() {
|
||||||
createServer: function() {
|
createServer: function() {
|
||||||
return {
|
return {
|
||||||
listenAsync: stubListenAsync,
|
listenAsync: stubListenAsync,
|
||||||
|
on: stub(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -979,6 +1012,7 @@ t.describe('#listenAsync()', function() {
|
||||||
createServer: function() {
|
createServer: function() {
|
||||||
return {
|
return {
|
||||||
listenAsync: stubListenAsync,
|
listenAsync: stubListenAsync,
|
||||||
|
on: stub(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1038,10 +1072,23 @@ t.describe('#closeAsync()', function() {
|
||||||
assert.strictEqual(err, assertError)
|
assert.strictEqual(err, assertError)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.test('it should call closeAllConnections', async function() {
|
||||||
|
const assertError = new Error('Pirate Fight')
|
||||||
|
let flaska = new Flaska()
|
||||||
|
flaska.server = {
|
||||||
|
close: stub(),
|
||||||
|
closeAllConnections: stub(),
|
||||||
|
}
|
||||||
|
flaska.server.closeAllConnections.throws(assertError)
|
||||||
|
let err = await assert.isRejected(flaska.closeAsync())
|
||||||
|
assert.strictEqual(err, assertError)
|
||||||
|
})
|
||||||
|
|
||||||
t.test('should otherwise work', async function() {
|
t.test('should otherwise work', async function() {
|
||||||
let flaska = new Flaska()
|
let flaska = new Flaska()
|
||||||
flaska.server = {
|
flaska.server = {
|
||||||
close: stub()
|
close: stub(),
|
||||||
|
closeAllConnections: stub(),
|
||||||
}
|
}
|
||||||
flaska.server.close.returnWith(function(cb) {
|
flaska.server.close.returnWith(function(cb) {
|
||||||
cb(null, { a: 1 })
|
cb(null, { a: 1 })
|
||||||
|
|
|
@ -19,13 +19,11 @@ t.describe('#requestStart()', function() {
|
||||||
flaska.onreserror(onResError)
|
flaska.onreserror(onResError)
|
||||||
flaska.requestEnded = onEnded
|
flaska.requestEnded = onEnded
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
try {
|
|
||||||
assert.ok(err)
|
assert.ok(err)
|
||||||
assert.strictEqual(assertReq.on.callCount, 1)
|
assert.strictEqual(assertReq.on.callCount, 1)
|
||||||
assert.strictEqual(assertRes.on.callCount, 2)
|
assert.strictEqual(assertRes.on.callCount, 2)
|
||||||
|
|
||||||
|
|
||||||
assert.strictEqual(assertRes.on.firstCall[0], 'error')
|
assert.strictEqual(assertRes.on.firstCall[0], 'error')
|
||||||
assert.strictEqual(typeof(assertRes.on.firstCall[1]), 'function')
|
assert.strictEqual(typeof(assertRes.on.firstCall[1]), 'function')
|
||||||
assertRes.on.firstCall[1](assertErrorTwo, ctx)
|
assertRes.on.firstCall[1](assertErrorTwo, ctx)
|
||||||
|
@ -46,12 +44,7 @@ t.describe('#requestStart()', function() {
|
||||||
assert.strictEqual(onReqError.callCount, 1)
|
assert.strictEqual(onReqError.callCount, 1)
|
||||||
assert.strictEqual(onReqError.firstCall[0], assertErrorOne)
|
assert.strictEqual(onReqError.firstCall[0], assertErrorOne)
|
||||||
assert.strictEqual(onReqError.firstCall[1], ctx)
|
assert.strictEqual(onReqError.firstCall[1], ctx)
|
||||||
|
})
|
||||||
// Test abort and close
|
|
||||||
|
|
||||||
cb()
|
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
flaska._beforeCompiled = function(ctx) {
|
flaska._beforeCompiled = function(ctx) {
|
||||||
throw new Error()
|
throw new Error()
|
||||||
}
|
}
|
||||||
|
@ -65,18 +58,13 @@ t.describe('#requestStart()', function() {
|
||||||
const assertRes = createRes({ b: 2 })
|
const assertRes = createRes({ b: 2 })
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
if (err && err !== assertError) return cb(err)
|
|
||||||
|
|
||||||
try {
|
|
||||||
assert.ok(err)
|
assert.ok(err)
|
||||||
assert.strictEqual(err, assertError)
|
assert.strictEqual(err, assertError)
|
||||||
assert.deepStrictEqual(ctx.state, {})
|
assert.deepStrictEqual(ctx.state, {})
|
||||||
assert.strictEqual(ctx.req, assertReq)
|
assert.strictEqual(ctx.req, assertReq)
|
||||||
assert.strictEqual(ctx.res, assertRes)
|
assert.strictEqual(ctx.res, assertRes)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
flaska._beforeCompiled = function(ctx) {
|
flaska._beforeCompiled = function(ctx) {
|
||||||
assert.strictEqual(ctx.req, assertReq)
|
assert.strictEqual(ctx.req, assertReq)
|
||||||
assert.strictEqual(ctx.res, assertRes)
|
assert.strictEqual(ctx.res, assertRes)
|
||||||
|
@ -99,18 +87,13 @@ t.describe('#requestStart()', function() {
|
||||||
return Promise.resolve().then(function() { return Promise.reject(assertError) })
|
return Promise.resolve().then(function() { return Promise.reject(assertError) })
|
||||||
}
|
}
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
if (err && err !== assertError) return cb(err)
|
|
||||||
|
|
||||||
try {
|
|
||||||
assert.ok(err)
|
assert.ok(err)
|
||||||
assert.strictEqual(err, assertError)
|
assert.strictEqual(err, assertError)
|
||||||
assert.deepStrictEqual(ctx.state, {})
|
assert.deepStrictEqual(ctx.state, {})
|
||||||
assert.strictEqual(ctx.req, assertReq)
|
assert.strictEqual(ctx.req, assertReq)
|
||||||
assert.strictEqual(ctx.res, assertRes)
|
assert.strictEqual(ctx.res, assertRes)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestStart(assertReq, assertRes)
|
flaska.requestStart(assertReq, assertRes)
|
||||||
})
|
})
|
||||||
|
@ -132,10 +115,8 @@ t.describe('#requestStart()', function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
if (err && err !== assertError) return cb(err)
|
|
||||||
|
|
||||||
try {
|
|
||||||
assert.ok(err)
|
assert.ok(err)
|
||||||
assert.strictEqual(err, assertError)
|
assert.strictEqual(err, assertError)
|
||||||
assert.strictEqual(ctx.url, assertPath)
|
assert.strictEqual(ctx.url, assertPath)
|
||||||
|
@ -157,14 +138,12 @@ t.describe('#requestStart()', function() {
|
||||||
|
|
||||||
assert.strictEqual(ctx.headers['Server'], 'Flaska')
|
assert.strictEqual(ctx.headers['Server'], 'Flaska')
|
||||||
assert.strictEqual(ctx.headers['X-Content-Type-Options'], 'nosniff')
|
assert.strictEqual(ctx.headers['X-Content-Type-Options'], 'nosniff')
|
||||||
assert.strictEqual(ctx.headers['Content-Security-Policy'], `default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data: blob:; object-src 'none'; frame-ancestors 'none'`)
|
assert.strictEqual(ctx.headers['Content-Security-Policy'], `default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data: blob:; font-src 'self' data:; object-src 'none'; frame-ancestors 'none'`)
|
||||||
assert.strictEqual(ctx.headers['Cross-Origin-Opener-Policy'], 'same-origin')
|
assert.strictEqual(ctx.headers['Cross-Origin-Opener-Policy'], 'same-origin')
|
||||||
assert.strictEqual(ctx.headers['Cross-Origin-Resource-Policy'], 'same-origin')
|
assert.strictEqual(ctx.headers['Cross-Origin-Resource-Policy'], 'same-origin')
|
||||||
assert.strictEqual(ctx.headers['Cross-Origin-Embedder-Policy'], 'require-corp')
|
assert.strictEqual(ctx.headers['Cross-Origin-Embedder-Policy'], 'require-corp')
|
||||||
assert.ok(new Date(ctx.headers['Date']).getDate())
|
assert.ok(new Date(ctx.headers['Date']).getDate())
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestStart(createReq({
|
flaska.requestStart(createReq({
|
||||||
url: assertPath + assertSearch,
|
url: assertPath + assertSearch,
|
||||||
|
@ -189,10 +168,7 @@ t.describe('#requestStart()', function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
if (err && err !== assertError) return cb(err)
|
|
||||||
|
|
||||||
try {
|
|
||||||
assert.ok(err)
|
assert.ok(err)
|
||||||
assert.strictEqual(err, assertError)
|
assert.strictEqual(err, assertError)
|
||||||
let keys = Object.keys(defaultHeaders)
|
let keys = Object.keys(defaultHeaders)
|
||||||
|
@ -201,9 +177,7 @@ t.describe('#requestStart()', function() {
|
||||||
assert.strictEqual(ctx.headers[key], defaultHeaders[key])
|
assert.strictEqual(ctx.headers[key], defaultHeaders[key])
|
||||||
}
|
}
|
||||||
assert.ok(ctx.headers['Date'])
|
assert.ok(ctx.headers['Date'])
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestStart(createReq({
|
flaska.requestStart(createReq({
|
||||||
url: '/',
|
url: '/',
|
||||||
|
@ -216,9 +190,13 @@ t.describe('#requestStart()', function() {
|
||||||
const assertMethod = 'test'
|
const assertMethod = 'test'
|
||||||
const assertPath = '/test/me'
|
const assertPath = '/test/me'
|
||||||
const assertSearch = '?asdf=test'
|
const assertSearch = '?asdf=test'
|
||||||
|
let calledBefore = false
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
flaska._beforeAsyncCompiled = function() { return Promise.resolve() }
|
flaska._beforeAsyncCompiled = function() {
|
||||||
|
calledBefore = true
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
flaska.routers.test = {
|
flaska.routers.test = {
|
||||||
match: function(path) {
|
match: function(path) {
|
||||||
|
@ -227,18 +205,14 @@ t.describe('#requestStart()', function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
if (err && err !== assertError) return cb(err)
|
|
||||||
|
|
||||||
try {
|
|
||||||
assert.ok(err)
|
assert.ok(err)
|
||||||
|
assert.ok(calledBefore)
|
||||||
assert.strictEqual(err, assertError)
|
assert.strictEqual(err, assertError)
|
||||||
assert.strictEqual(ctx.url, assertPath)
|
assert.strictEqual(ctx.url, assertPath)
|
||||||
assert.strictEqual(ctx.search, assertSearch)
|
assert.strictEqual(ctx.search, assertSearch)
|
||||||
assert.strictEqual(ctx.method, assertMethod)
|
assert.strictEqual(ctx.method, assertMethod)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestStart(createReq({
|
flaska.requestStart(createReq({
|
||||||
url: assertPath + assertSearch,
|
url: assertPath + assertSearch,
|
||||||
|
@ -246,7 +220,7 @@ t.describe('#requestStart()', function() {
|
||||||
}), createRes())
|
}), createRes())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('calls handleMiddleware correctly', function(cb) {
|
t.test('calls handlers correctly', function(cb) {
|
||||||
const assertError = new Error('test')
|
const assertError = new Error('test')
|
||||||
const assertMiddles = [1, 2]
|
const assertMiddles = [1, 2]
|
||||||
const assertParams = { a: 1, b: 2 }
|
const assertParams = { a: 1, b: 2 }
|
||||||
|
@ -257,30 +231,24 @@ t.describe('#requestStart()', function() {
|
||||||
|
|
||||||
flaska.routers.GET.match = function() {
|
flaska.routers.GET.match = function() {
|
||||||
return {
|
return {
|
||||||
handler: function() {},
|
path: { handlers: assertMiddles, },
|
||||||
middlewares: assertMiddles,
|
|
||||||
params: assertParams,
|
params: assertParams,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flaska.handleMiddleware = function(ctx, middles, index) {
|
flaska.runHandlers = function(ctx, middles, index) {
|
||||||
assert.strictEqual(index, 0)
|
assert.strictEqual(index, 0)
|
||||||
assert.strictEqual(middles, assertMiddles)
|
assert.strictEqual(middles, assertMiddles)
|
||||||
checkMiddleCtx = ctx
|
checkMiddleCtx = ctx
|
||||||
throw assertError
|
throw assertError
|
||||||
}
|
}
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
if (err && err !== assertError) return cb(err)
|
|
||||||
|
|
||||||
try {
|
|
||||||
assert.ok(err)
|
assert.ok(err)
|
||||||
assert.ok(ctx)
|
assert.ok(ctx)
|
||||||
assert.strictEqual(err, assertError)
|
assert.strictEqual(err, assertError)
|
||||||
assert.strictEqual(ctx, checkMiddleCtx)
|
assert.strictEqual(ctx, checkMiddleCtx)
|
||||||
assert.strictEqual(ctx.params, assertParams)
|
assert.strictEqual(ctx.params, assertParams)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestStart(createReq({
|
flaska.requestStart(createReq({
|
||||||
url: '',
|
url: '',
|
||||||
|
@ -301,17 +269,12 @@ t.describe('#requestStart()', function() {
|
||||||
flaska.get('/:id', handler)
|
flaska.get('/:id', handler)
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
if (err && err !== assertError) return cb(err)
|
|
||||||
|
|
||||||
try {
|
|
||||||
assert.ok(err)
|
assert.ok(err)
|
||||||
assert.ok(ctx)
|
assert.ok(ctx)
|
||||||
assert.strictEqual(err, assertError)
|
assert.strictEqual(err, assertError)
|
||||||
assert.strictEqual(ctx, checkHandlerCtx)
|
assert.strictEqual(ctx, checkHandlerCtx)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestStart(createReq({
|
flaska.requestStart(createReq({
|
||||||
url: '/test',
|
url: '/test',
|
||||||
|
@ -331,16 +294,12 @@ t.describe('#requestStart()', function() {
|
||||||
flaska.get('/test', function() { throw new Error('should not be called') })
|
flaska.get('/test', function() { throw new Error('should not be called') })
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
if (err) return cb(err)
|
assert.notOk(err)
|
||||||
|
|
||||||
try {
|
|
||||||
assert.ok(ctx)
|
assert.ok(ctx)
|
||||||
assert.strictEqual(on404Error.callCount, 1)
|
assert.strictEqual(on404Error.callCount, 1)
|
||||||
assert.strictEqual(on404Error.firstCall[0], ctx)
|
assert.strictEqual(on404Error.firstCall[0], ctx)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestStart(createReq({
|
flaska.requestStart(createReq({
|
||||||
url: '/nope',
|
url: '/nope',
|
||||||
|
@ -361,17 +320,11 @@ t.describe('#requestStart()', function() {
|
||||||
flaska.get('/test', function() { throw new Error('should not be called') })
|
flaska.get('/test', function() { throw new Error('should not be called') })
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
if (err && err !== assertError) return cb(err)
|
assert.strictEqual(err, assertError)
|
||||||
|
|
||||||
try {
|
|
||||||
assert.ok(err)
|
|
||||||
assert.ok(ctx)
|
assert.ok(ctx)
|
||||||
assert.strictEqual(ctx, checkCtx)
|
assert.strictEqual(ctx, checkCtx)
|
||||||
assert.strictEqual(err, assertError)
|
})
|
||||||
cb()
|
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestStart(createReq({
|
flaska.requestStart(createReq({
|
||||||
url: '/nope',
|
url: '/nope',
|
||||||
|
@ -391,17 +344,11 @@ t.describe('#requestStart()', function() {
|
||||||
flaska.get('/test', function() { throw new Error('should not be called') })
|
flaska.get('/test', function() { throw new Error('should not be called') })
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
if (err && err !== assertError) return cb(err)
|
assert.strictEqual(err, assertError)
|
||||||
|
|
||||||
try {
|
|
||||||
assert.ok(err)
|
|
||||||
assert.ok(ctx)
|
assert.ok(ctx)
|
||||||
assert.strictEqual(ctx, checkCtx)
|
assert.strictEqual(ctx, checkCtx)
|
||||||
assert.strictEqual(err, assertError)
|
})
|
||||||
cb()
|
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestStart(createReq({
|
flaska.requestStart(createReq({
|
||||||
url: '/nope',
|
url: '/nope',
|
||||||
|
@ -421,17 +368,11 @@ t.describe('#requestStart()', function() {
|
||||||
flaska.get('/test', middles, function() { throw new Error('should not be called') })
|
flaska.get('/test', middles, function() { throw new Error('should not be called') })
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
if (err && err !== assertError) return cb(err)
|
assert.strictEqual(err, assertError)
|
||||||
|
|
||||||
try {
|
|
||||||
assert.ok(err)
|
|
||||||
assert.ok(ctx)
|
assert.ok(ctx)
|
||||||
assert.strictEqual(ctx, checkCtx)
|
assert.strictEqual(ctx, checkCtx)
|
||||||
assert.strictEqual(err, assertError)
|
})
|
||||||
cb()
|
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestStart(createReq({
|
flaska.requestStart(createReq({
|
||||||
url: '/test',
|
url: '/test',
|
||||||
|
@ -449,18 +390,12 @@ t.describe('#requestStart()', function() {
|
||||||
let flaska = new Flaska({}, faker)
|
let flaska = new Flaska({}, faker)
|
||||||
flaska.get('/::path', handler)
|
flaska.get('/::path', handler)
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
flaska.handleMiddleware = function() {
|
|
||||||
throw new Error('should not be called')
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
try {
|
|
||||||
assert.notOk(err)
|
assert.notOk(err)
|
||||||
assert.ok(ctx)
|
assert.ok(ctx)
|
||||||
assert.strictEqual(ctx.body, assertBody)
|
assert.strictEqual(ctx.body, assertBody)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestStart(createReq({
|
flaska.requestStart(createReq({
|
||||||
url: '/test/something/here',
|
url: '/test/something/here',
|
||||||
|
@ -468,7 +403,7 @@ t.describe('#requestStart()', function() {
|
||||||
}), createRes())
|
}), createRes())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('calls handleMiddleware correctly if is promise', function(cb) {
|
t.test('calls runHandlers correctly if is promise', function(cb) {
|
||||||
const assertError = new Error('test')
|
const assertError = new Error('test')
|
||||||
const assertMiddles = [1]
|
const assertMiddles = [1]
|
||||||
|
|
||||||
|
@ -477,24 +412,19 @@ t.describe('#requestStart()', function() {
|
||||||
|
|
||||||
flaska.routers.GET.match = function() {
|
flaska.routers.GET.match = function() {
|
||||||
return {
|
return {
|
||||||
handler: function() {},
|
path: { handlers: function() {}, },
|
||||||
middlewares: assertMiddles,
|
middlewares: assertMiddles,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flaska.handleMiddleware = function() {
|
flaska.runHandlers = function() {
|
||||||
return Promise.resolve().then(function() { return Promise.reject(assertError) })
|
return Promise.resolve().then(function() { return Promise.reject(assertError) })
|
||||||
}
|
}
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
if (err && err !== assertError) return cb(err)
|
|
||||||
|
|
||||||
try {
|
|
||||||
assert.ok(err)
|
assert.ok(err)
|
||||||
assert.ok(ctx)
|
assert.ok(ctx)
|
||||||
assert.strictEqual(err, assertError)
|
assert.strictEqual(err, assertError)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestStart(createReq({
|
flaska.requestStart(createReq({
|
||||||
url: '',
|
url: '',
|
||||||
|
@ -515,17 +445,12 @@ t.describe('#requestStart()', function() {
|
||||||
flaska.get('/:id', [function() { return Promise.resolve() }], handler)
|
flaska.get('/:id', [function() { return Promise.resolve() }], handler)
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
if (err && err !== assertError) return cb(err)
|
|
||||||
|
|
||||||
try {
|
|
||||||
assert.ok(err)
|
assert.ok(err)
|
||||||
assert.ok(ctx)
|
assert.ok(ctx)
|
||||||
assert.strictEqual(err, assertError)
|
assert.strictEqual(err, assertError)
|
||||||
assert.strictEqual(ctx, checkHandlerCtx)
|
assert.strictEqual(ctx, checkHandlerCtx)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestStart(createReq({
|
flaska.requestStart(createReq({
|
||||||
url: '/test',
|
url: '/test',
|
||||||
|
@ -546,17 +471,12 @@ t.describe('#requestStart()', function() {
|
||||||
flaska.get('/:id', [function() { return Promise.resolve() }], handler)
|
flaska.get('/:id', [function() { return Promise.resolve() }], handler)
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
if (err && err !== assertError) return cb(err)
|
|
||||||
|
|
||||||
try {
|
|
||||||
assert.ok(err)
|
assert.ok(err)
|
||||||
assert.ok(ctx)
|
assert.ok(ctx)
|
||||||
assert.strictEqual(err, assertError)
|
assert.strictEqual(err, assertError)
|
||||||
assert.strictEqual(ctx, checkHandlerCtx)
|
assert.strictEqual(ctx, checkHandlerCtx)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestStart(createReq({
|
flaska.requestStart(createReq({
|
||||||
url: '/test',
|
url: '/test',
|
||||||
|
@ -585,15 +505,12 @@ t.describe('#requestStart()', function() {
|
||||||
flaska.get('/::path', [middle], handler)
|
flaska.get('/::path', [middle], handler)
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
try {
|
|
||||||
assert.notOk(err)
|
assert.notOk(err)
|
||||||
assert.ok(ctx)
|
assert.ok(ctx)
|
||||||
assert.strictEqual(ctx.body, assertBody)
|
assert.strictEqual(ctx.body, assertBody)
|
||||||
assert.strictEqual(ctx.state, assertState)
|
assert.strictEqual(ctx.state, assertState)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestStart(createReq({
|
flaska.requestStart(createReq({
|
||||||
url: '/test/something/here',
|
url: '/test/something/here',
|
||||||
|
@ -616,17 +533,12 @@ t.describe('#requestStart()', function() {
|
||||||
flaska.get('/:id', handler)
|
flaska.get('/:id', handler)
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
if (err && err !== assertError) return cb(err)
|
|
||||||
|
|
||||||
try {
|
|
||||||
assert.ok(err)
|
assert.ok(err)
|
||||||
assert.ok(ctx)
|
assert.ok(ctx)
|
||||||
assert.strictEqual(err, assertError)
|
assert.strictEqual(err, assertError)
|
||||||
assert.strictEqual(ctx, checkHandlerCtx)
|
assert.strictEqual(ctx, checkHandlerCtx)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestStart(createReq({
|
flaska.requestStart(createReq({
|
||||||
url: '/test',
|
url: '/test',
|
||||||
|
@ -647,18 +559,11 @@ t.describe('#requestStart()', function() {
|
||||||
flaska.get('/::path', [], handler)
|
flaska.get('/::path', [], handler)
|
||||||
flaska.compile()
|
flaska.compile()
|
||||||
|
|
||||||
flaska.handleMiddleware = function() {
|
flaska.requestEnd = cb.finish(function(err, ctx) {
|
||||||
throw new Error('should not be called')
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestEnd = function(err, ctx) {
|
|
||||||
try {
|
|
||||||
assert.notOk(err)
|
assert.notOk(err)
|
||||||
assert.ok(ctx)
|
assert.ok(ctx)
|
||||||
assert.strictEqual(ctx.body, assertBody)
|
assert.strictEqual(ctx.body, assertBody)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
|
|
||||||
flaska.requestStart(createReq({
|
flaska.requestStart(createReq({
|
||||||
url: '/test/something/here',
|
url: '/test/something/here',
|
||||||
|
|
|
@ -12,8 +12,7 @@ t.describe('#requestEnd()', function() {
|
||||||
const assertStatus = 501
|
const assertStatus = 501
|
||||||
// Calculated manually just in case
|
// Calculated manually just in case
|
||||||
const assertBodyLength = 7
|
const assertBodyLength = 7
|
||||||
let onFinish = function(body) {
|
let onFinish = cb.finish(function(body) {
|
||||||
try {
|
|
||||||
assert.strictEqual(ctx.status, assertStatus)
|
assert.strictEqual(ctx.status, assertStatus)
|
||||||
assert.strictEqual(ctx.body, assertBody)
|
assert.strictEqual(ctx.body, assertBody)
|
||||||
assert.strictEqual(ctx.headers['Content-Type'], 'application/json; charset=utf-8')
|
assert.strictEqual(ctx.headers['Content-Type'], 'application/json; charset=utf-8')
|
||||||
|
@ -23,9 +22,7 @@ t.describe('#requestEnd()', function() {
|
||||||
assert.ok(ctx.res.writeHead.called)
|
assert.ok(ctx.res.writeHead.called)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
const ctx = createCtx({}, onFinish)
|
const ctx = createCtx({}, onFinish)
|
||||||
|
|
||||||
let flaska = new Flaska({}, fakerHttp, fakeStream)
|
let flaska = new Flaska({}, fakerHttp, fakeStream)
|
||||||
|
@ -43,14 +40,11 @@ t.describe('#requestEnd()', function() {
|
||||||
const assertErrorNotSeen = new Error('should not be seen')
|
const assertErrorNotSeen = new Error('should not be seen')
|
||||||
const assertError = new Error('test')
|
const assertError = new Error('test')
|
||||||
|
|
||||||
let onFinish = function(body) {
|
let onFinish = cb.finish(function(body) {
|
||||||
try {
|
|
||||||
assert.strictEqual(ctx.status, 500)
|
assert.strictEqual(ctx.status, 500)
|
||||||
assert.strictEqual(ctx.body.status, 500)
|
assert.strictEqual(ctx.body.status, 500)
|
||||||
assert.strictEqual(ctx.body.message, 'Internal Server Error')
|
assert.strictEqual(ctx.body.message, 'Internal Server Error')
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
const ctx = createCtx({}, onFinish)
|
const ctx = createCtx({}, onFinish)
|
||||||
|
|
||||||
let flaska = new Flaska({}, fakerHttp, fakeStream)
|
let flaska = new Flaska({}, fakerHttp, fakeStream)
|
||||||
|
@ -70,8 +64,7 @@ t.describe('#requestEnd()', function() {
|
||||||
// Calculated manually just in case
|
// Calculated manually just in case
|
||||||
const assertBodyLength = 7
|
const assertBodyLength = 7
|
||||||
const assertBody = { a: 1 }
|
const assertBody = { a: 1 }
|
||||||
let onFinish = function(body) {
|
let onFinish = cb.finish(function(body) {
|
||||||
try {
|
|
||||||
assert.strictEqual(ctx.status, assertStatus)
|
assert.strictEqual(ctx.status, assertStatus)
|
||||||
assert.strictEqual(ctx.body, assertBody)
|
assert.strictEqual(ctx.body, assertBody)
|
||||||
assert.strictEqual(ctx.headers['Content-Type'], 'application/json; charset=utf-8')
|
assert.strictEqual(ctx.headers['Content-Type'], 'application/json; charset=utf-8')
|
||||||
|
@ -87,9 +80,39 @@ t.describe('#requestEnd()', function() {
|
||||||
assert.ok(ctx.res.writeHead.called)
|
assert.ok(ctx.res.writeHead.called)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
const ctx = createCtx({
|
||||||
|
method: method,
|
||||||
|
status: assertStatus,
|
||||||
|
}, onFinish)
|
||||||
|
ctx.body = assertBody
|
||||||
|
|
||||||
|
let flaska = new Flaska({}, fakerHttp, fakeStream)
|
||||||
|
flaska.requestEnd(null, ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('call res and end correctly when dealing with buffers', function(cb) {
|
||||||
|
const assertStatus = 202
|
||||||
|
// Calculated manually just in case
|
||||||
|
const assertBodyLength = 11
|
||||||
|
const assertBody = Buffer.from('hello world')
|
||||||
|
let onFinish = cb.finish(function(body) {
|
||||||
|
assert.strictEqual(ctx.status, assertStatus)
|
||||||
|
assert.strictEqual(ctx.body, assertBody)
|
||||||
|
assert.strictEqual(ctx.headers['Content-Type'], 'application/octet-stream')
|
||||||
|
assert.strictEqual(ctx.headers['Content-Length'], assertBodyLength)
|
||||||
|
|
||||||
|
if (method === 'GET') {
|
||||||
|
assert.ok(body)
|
||||||
|
assert.strictEqual(body, assertBody)
|
||||||
|
} else {
|
||||||
|
assert.notOk(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert.ok(ctx.res.writeHead.called)
|
||||||
|
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
||||||
|
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
||||||
|
})
|
||||||
const ctx = createCtx({
|
const ctx = createCtx({
|
||||||
method: method,
|
method: method,
|
||||||
status: assertStatus,
|
status: assertStatus,
|
||||||
|
@ -104,8 +127,7 @@ t.describe('#requestEnd()', function() {
|
||||||
// Calculated manually just in case
|
// Calculated manually just in case
|
||||||
const assertBodyLength = 0
|
const assertBodyLength = 0
|
||||||
const assertBody = null
|
const assertBody = null
|
||||||
let onFinish = function(body) {
|
let onFinish = cb.finish(function(body) {
|
||||||
try {
|
|
||||||
assert.strictEqual(ctx.status, 204)
|
assert.strictEqual(ctx.status, 204)
|
||||||
assert.strictEqual(ctx.body, assertBody)
|
assert.strictEqual(ctx.body, assertBody)
|
||||||
assert.notOk(ctx.headers['Content-Type'])
|
assert.notOk(ctx.headers['Content-Type'])
|
||||||
|
@ -116,9 +138,7 @@ t.describe('#requestEnd()', function() {
|
||||||
assert.ok(ctx.res.writeHead.called)
|
assert.ok(ctx.res.writeHead.called)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
const ctx = createCtx({
|
const ctx = createCtx({
|
||||||
method: method,
|
method: method,
|
||||||
status: 200,
|
status: 200,
|
||||||
|
@ -133,8 +153,7 @@ t.describe('#requestEnd()', function() {
|
||||||
const assertStatus = 202
|
const assertStatus = 202
|
||||||
// Calculated manually just in case
|
// Calculated manually just in case
|
||||||
const assertBody = null
|
const assertBody = null
|
||||||
let onFinish = function(body) {
|
let onFinish = cb.finish(function(body) {
|
||||||
try {
|
|
||||||
assert.strictEqual(ctx.status, assertStatus)
|
assert.strictEqual(ctx.status, assertStatus)
|
||||||
assert.strictEqual(ctx.body, assertBody)
|
assert.strictEqual(ctx.body, assertBody)
|
||||||
assert.notOk(ctx.headers['Content-Type'])
|
assert.notOk(ctx.headers['Content-Type'])
|
||||||
|
@ -145,9 +164,7 @@ t.describe('#requestEnd()', function() {
|
||||||
assert.ok(ctx.res.writeHead.called)
|
assert.ok(ctx.res.writeHead.called)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
const ctx = createCtx({
|
const ctx = createCtx({
|
||||||
method: method,
|
method: method,
|
||||||
status: assertStatus,
|
status: assertStatus,
|
||||||
|
@ -163,8 +180,7 @@ t.describe('#requestEnd()', function() {
|
||||||
// Calculated manually just in case
|
// Calculated manually just in case
|
||||||
const assertBodyLength = 4
|
const assertBodyLength = 4
|
||||||
const assertBody = 'eða'
|
const assertBody = 'eða'
|
||||||
let onFinish = function(body) {
|
let onFinish = cb.finish(function(body) {
|
||||||
try {
|
|
||||||
assert.strictEqual(ctx.status, assertStatus)
|
assert.strictEqual(ctx.status, assertStatus)
|
||||||
assert.strictEqual(ctx.body, assertBody)
|
assert.strictEqual(ctx.body, assertBody)
|
||||||
assert.strictEqual(ctx.headers['Content-Type'], 'text/plain; charset=utf-8')
|
assert.strictEqual(ctx.headers['Content-Type'], 'text/plain; charset=utf-8')
|
||||||
|
@ -180,9 +196,7 @@ t.describe('#requestEnd()', function() {
|
||||||
assert.ok(ctx.res.writeHead.called)
|
assert.ok(ctx.res.writeHead.called)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
const ctx = createCtx({
|
const ctx = createCtx({
|
||||||
method: method,
|
method: method,
|
||||||
status: assertStatus,
|
status: assertStatus,
|
||||||
|
@ -198,8 +212,7 @@ t.describe('#requestEnd()', function() {
|
||||||
// Calculated manually just in case
|
// Calculated manually just in case
|
||||||
const assertBodyLength = 7
|
const assertBodyLength = 7
|
||||||
const assertBody = 4214124
|
const assertBody = 4214124
|
||||||
let onFinish = function(body) {
|
let onFinish = cb.finish(function(body) {
|
||||||
try {
|
|
||||||
assert.strictEqual(ctx.status, assertStatus)
|
assert.strictEqual(ctx.status, assertStatus)
|
||||||
assert.strictEqual(ctx.body, assertBody)
|
assert.strictEqual(ctx.body, assertBody)
|
||||||
assert.strictEqual(ctx.headers['Content-Type'], 'text/plain; charset=utf-8')
|
assert.strictEqual(ctx.headers['Content-Type'], 'text/plain; charset=utf-8')
|
||||||
|
@ -214,9 +227,7 @@ t.describe('#requestEnd()', function() {
|
||||||
assert.ok(ctx.res.writeHead.called)
|
assert.ok(ctx.res.writeHead.called)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
const ctx = createCtx({
|
const ctx = createCtx({
|
||||||
method: method,
|
method: method,
|
||||||
status: assertStatus,
|
status: assertStatus,
|
||||||
|
@ -242,8 +253,7 @@ t.describe('#requestEnd()', function() {
|
||||||
|
|
||||||
let body = new FileResponse()
|
let body = new FileResponse()
|
||||||
let assertBody = { pipe: function() {} }
|
let assertBody = { pipe: function() {} }
|
||||||
let onFinish = function(source, target, callback) {
|
let onFinish = cb.finish(function(source, target, callback) {
|
||||||
try {
|
|
||||||
assert.ok(assertHandle.called)
|
assert.ok(assertHandle.called)
|
||||||
assert.ok(ctx.res.writeHead)
|
assert.ok(ctx.res.writeHead)
|
||||||
assert.strictEqual(ctx.body, assertBody)
|
assert.strictEqual(ctx.body, assertBody)
|
||||||
|
@ -255,9 +265,7 @@ t.describe('#requestEnd()', function() {
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
||||||
assert.strictEqual(ctx.headers['Content-Type'], assertContentType)
|
assert.strictEqual(ctx.headers['Content-Type'], assertContentType)
|
||||||
assert.strictEqual(ctx.headers['Content-Length'], assertContentLength)
|
assert.strictEqual(ctx.headers['Content-Length'], assertContentLength)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
const ctx = createCtx({
|
const ctx = createCtx({
|
||||||
})
|
})
|
||||||
ctx.body = body
|
ctx.body = body
|
||||||
|
@ -278,8 +286,7 @@ t.describe('#requestEnd()', function() {
|
||||||
const assertType = 'herp/derp'
|
const assertType = 'herp/derp'
|
||||||
const assertBody = { pipe: function() {} }
|
const assertBody = { pipe: function() {} }
|
||||||
|
|
||||||
let onFinish = function(source, target, callback) {
|
let onFinish = cb.finish(function(source, target, callback) {
|
||||||
try {
|
|
||||||
assert.strictEqual(ctx.status, assertStatus)
|
assert.strictEqual(ctx.status, assertStatus)
|
||||||
assert.strictEqual(ctx.headers['Content-Type'], assertType)
|
assert.strictEqual(ctx.headers['Content-Type'], assertType)
|
||||||
assert.strictEqual(source, assertBody)
|
assert.strictEqual(source, assertBody)
|
||||||
|
@ -288,9 +295,7 @@ t.describe('#requestEnd()', function() {
|
||||||
assert.ok(ctx.res.writeHead.called)
|
assert.ok(ctx.res.writeHead.called)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
const ctx = createCtx({
|
const ctx = createCtx({
|
||||||
status: assertStatus,
|
status: assertStatus,
|
||||||
})
|
})
|
||||||
|
@ -315,8 +320,7 @@ t.describe('#requestEnd()', function() {
|
||||||
})
|
})
|
||||||
|
|
||||||
let body = new FileResponse()
|
let body = new FileResponse()
|
||||||
let onFinish = function(body) {
|
let onFinish = cb.finish(function(body) {
|
||||||
try {
|
|
||||||
assert.ok(assertHandle.called)
|
assert.ok(assertHandle.called)
|
||||||
assert.ok(ctx.res.writeHead)
|
assert.ok(ctx.res.writeHead)
|
||||||
assert.strictEqual(ctx.body, null)
|
assert.strictEqual(ctx.body, null)
|
||||||
|
@ -326,9 +330,8 @@ t.describe('#requestEnd()', function() {
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
||||||
assert.strictEqual(ctx.headers['Content-Type'], assertContentType)
|
assert.strictEqual(ctx.headers['Content-Type'], assertContentType)
|
||||||
assert.strictEqual(ctx.headers['Content-Length'], assertContentLength)
|
assert.strictEqual(ctx.headers['Content-Length'], assertContentLength)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
const ctx = createCtx({
|
const ctx = createCtx({
|
||||||
method: method,
|
method: method,
|
||||||
}, onFinish)
|
}, onFinish)
|
||||||
|
@ -349,8 +352,7 @@ t.describe('#requestEnd()', function() {
|
||||||
const assertType = 'herp/derp'
|
const assertType = 'herp/derp'
|
||||||
const assertBody = { pipe: function() {}, destroy: stub() }
|
const assertBody = { pipe: function() {}, destroy: stub() }
|
||||||
|
|
||||||
let onFinish = function(body) {
|
let onFinish = cb.finish(function(body) {
|
||||||
try {
|
|
||||||
assert.strictEqual(ctx.status, assertStatus)
|
assert.strictEqual(ctx.status, assertStatus)
|
||||||
assert.strictEqual(ctx.headers['Content-Type'], assertType)
|
assert.strictEqual(ctx.headers['Content-Type'], assertType)
|
||||||
assert.notOk(ctx.headers['Content-Length'])
|
assert.notOk(ctx.headers['Content-Length'])
|
||||||
|
@ -360,9 +362,7 @@ t.describe('#requestEnd()', function() {
|
||||||
assert.ok(ctx.res.writeHead.called)
|
assert.ok(ctx.res.writeHead.called)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
const ctx = createCtx({
|
const ctx = createCtx({
|
||||||
method: method,
|
method: method,
|
||||||
status: assertStatus,
|
status: assertStatus,
|
||||||
|
@ -405,17 +405,14 @@ t.describe('#requestEnd()', function() {
|
||||||
const assertStatus = 209
|
const assertStatus = 209
|
||||||
const assertBody = 'test'
|
const assertBody = 'test'
|
||||||
const assertType = 'something/else'
|
const assertType = 'something/else'
|
||||||
let onFinish = function(body) {
|
let onFinish = cb.finish(function(body) {
|
||||||
try {
|
|
||||||
assert.strictEqual(ctx.status, assertStatus)
|
assert.strictEqual(ctx.status, assertStatus)
|
||||||
assert.strictEqual(ctx.headers['Content-Type'], assertType)
|
assert.strictEqual(ctx.headers['Content-Type'], assertType)
|
||||||
assert.strictEqual(body, assertBody)
|
assert.strictEqual(body, assertBody)
|
||||||
assert.ok(ctx.res.writeHead.called)
|
assert.ok(ctx.res.writeHead.called)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
const ctx = createCtx({
|
const ctx = createCtx({
|
||||||
status: assertStatus,
|
status: assertStatus,
|
||||||
body: assertBody,
|
body: assertBody,
|
||||||
|
@ -427,8 +424,7 @@ t.describe('#requestEnd()', function() {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('call pipe should have default type', function(cb) {
|
t.test('call pipe should have default type', function(cb) {
|
||||||
let onFinish = function(source, target) {
|
let onFinish = cb.finish(function(source, target) {
|
||||||
try {
|
|
||||||
assert.strictEqual(ctx.status, 200)
|
assert.strictEqual(ctx.status, 200)
|
||||||
assert.strictEqual(ctx.headers['Content-Type'], 'application/octet-stream')
|
assert.strictEqual(ctx.headers['Content-Type'], 'application/octet-stream')
|
||||||
assert.strictEqual(source, ctx.body)
|
assert.strictEqual(source, ctx.body)
|
||||||
|
@ -436,9 +432,7 @@ t.describe('#requestEnd()', function() {
|
||||||
assert.ok(ctx.res.writeHead.called)
|
assert.ok(ctx.res.writeHead.called)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
const ctx = createCtx({})
|
const ctx = createCtx({})
|
||||||
ctx.body = { pipe: function() {} }
|
ctx.body = { pipe: function() {} }
|
||||||
fakeStream.pipeline = onFinish
|
fakeStream.pipeline = onFinish
|
||||||
|
@ -456,8 +450,7 @@ t.describe('#requestEnd()', function() {
|
||||||
|
|
||||||
tests.forEach(function(test) {
|
tests.forEach(function(test) {
|
||||||
t.test(`call pipe with file extension ${test[0]} should mimetype ${test[1]}`, function(cb) {
|
t.test(`call pipe with file extension ${test[0]} should mimetype ${test[1]}`, function(cb) {
|
||||||
let onFinish = function(source, target) {
|
let onFinish = cb.finish(function(source, target) {
|
||||||
try {
|
|
||||||
assert.strictEqual(ctx.status, 200)
|
assert.strictEqual(ctx.status, 200)
|
||||||
assert.strictEqual(ctx.headers['Content-Type'], test[1])
|
assert.strictEqual(ctx.headers['Content-Type'], test[1])
|
||||||
assert.strictEqual(source, ctx.body)
|
assert.strictEqual(source, ctx.body)
|
||||||
|
@ -465,9 +458,7 @@ t.describe('#requestEnd()', function() {
|
||||||
assert.ok(ctx.res.writeHead.called)
|
assert.ok(ctx.res.writeHead.called)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
const ctx = createCtx({})
|
const ctx = createCtx({})
|
||||||
ctx.body = { pipe: function() {}, path: './bla/test/temp.' + test[0] }
|
ctx.body = { pipe: function() {}, path: './bla/test/temp.' + test[0] }
|
||||||
fakeStream.pipeline = onFinish
|
fakeStream.pipeline = onFinish
|
||||||
|
@ -495,17 +486,14 @@ t.describe('#requestEnd()', function() {
|
||||||
const assertStatus = status
|
const assertStatus = status
|
||||||
const assertNotBody = 'test'
|
const assertNotBody = 'test'
|
||||||
const assertNotType = 'something/else'
|
const assertNotType = 'something/else'
|
||||||
let onFinish = function(body) {
|
let onFinish = cb.finish(function(body) {
|
||||||
try {
|
|
||||||
assert.strictEqual(ctx.status, assertStatus)
|
assert.strictEqual(ctx.status, assertStatus)
|
||||||
assert.strictEqual(ctx.res.setHeader.callCount, 0)
|
assert.strictEqual(ctx.res.setHeader.callCount, 0)
|
||||||
assert.ok(ctx.res.writeHead.called)
|
assert.ok(ctx.res.writeHead.called)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
assert.strictEqual(ctx.res.writeHead.firstCall[0], ctx.status)
|
||||||
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
assert.strictEqual(ctx.res.writeHead.firstCall[1], ctx.headers)
|
||||||
assert.notOk(body)
|
assert.notOk(body)
|
||||||
cb()
|
})
|
||||||
} catch (err) { cb(err) }
|
|
||||||
}
|
|
||||||
const ctx = createCtx({
|
const ctx = createCtx({
|
||||||
status: assertStatus,
|
status: assertStatus,
|
||||||
body: assertNotBody,
|
body: assertNotBody,
|
||||||
|
|
|
@ -6,7 +6,7 @@ const indexMap = [
|
||||||
'thirdCall',
|
'thirdCall',
|
||||||
]
|
]
|
||||||
|
|
||||||
export function fakeHttp(inj1, inj2) {
|
export function fakeHttp(inj1, inj2, inj3) {
|
||||||
let intermediate = {
|
let intermediate = {
|
||||||
createServer: function(cb) {
|
createServer: function(cb) {
|
||||||
if (inj1) inj1(cb)
|
if (inj1) inj1(cb)
|
||||||
|
@ -15,6 +15,9 @@ export function fakeHttp(inj1, inj2) {
|
||||||
listen: function(port, ip, cb) {
|
listen: function(port, ip, cb) {
|
||||||
if (inj2) inj2(port, ip, cb)
|
if (inj2) inj2(port, ip, cb)
|
||||||
else if (cb) cb()
|
else if (cb) cb()
|
||||||
|
},
|
||||||
|
on: function(name, handler) {
|
||||||
|
if (inj3) inj3(name, handler)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
128
test/http.body.json.test.mjs
Normal file
128
test/http.body.json.test.mjs
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
import { Eltro as t, assert, stub } from 'eltro'
|
||||||
|
import { setTimeout } from 'timers/promises'
|
||||||
|
import { Flaska, JsonHandler } from '../flaska.mjs'
|
||||||
|
import Client from './client.mjs'
|
||||||
|
|
||||||
|
const port = 51024
|
||||||
|
const log = {
|
||||||
|
fatal: stub(),
|
||||||
|
error: stub(),
|
||||||
|
warn: stub(),
|
||||||
|
info: stub(),
|
||||||
|
debug: stub(),
|
||||||
|
trace: stub(),
|
||||||
|
log: stub(),
|
||||||
|
}
|
||||||
|
const flaska = new Flaska({ log })
|
||||||
|
const client = new Client(port)
|
||||||
|
|
||||||
|
let reqBody = null
|
||||||
|
|
||||||
|
flaska.after(function(ctx) {
|
||||||
|
if (ctx.aborted) return
|
||||||
|
ctx.log.info(ctx.status)
|
||||||
|
})
|
||||||
|
|
||||||
|
flaska.post('/json', JsonHandler(), function(ctx) {
|
||||||
|
ctx.body = { success: true }
|
||||||
|
reqBody = ctx.req.body
|
||||||
|
})
|
||||||
|
flaska.post('/json/slow', JsonHandler(), async function(ctx) {
|
||||||
|
await setTimeout(300)
|
||||||
|
ctx.body = { success: true }
|
||||||
|
ctx.status = 201
|
||||||
|
reqBody = ctx.req.body
|
||||||
|
})
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
log.fatal.reset()
|
||||||
|
log.error.reset()
|
||||||
|
log.warn.reset()
|
||||||
|
log.info.reset()
|
||||||
|
log.debug.reset()
|
||||||
|
log.trace.reset()
|
||||||
|
log.log.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
t.before(function() {
|
||||||
|
return flaska.listenAsync(port)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('/json', function() {
|
||||||
|
t.beforeEach(function() {
|
||||||
|
log.info.reset()
|
||||||
|
})
|
||||||
|
t.test('should return success and store body', async function() {
|
||||||
|
const assertBody = { a: '' }
|
||||||
|
for (let i = 0; i < 1010; i++) {
|
||||||
|
assertBody.a += 'aaaaaaaaaa'
|
||||||
|
}
|
||||||
|
reqBody = null
|
||||||
|
let body = await client.post('/json', assertBody)
|
||||||
|
assert.deepEqual(body, { success: true })
|
||||||
|
assert.deepStrictEqual(reqBody, assertBody)
|
||||||
|
assert.strictEqual(log.info.callCount, 1)
|
||||||
|
assert.strictEqual(log.info.firstCall[0], 200)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should return and log correctly', async function() {
|
||||||
|
const assertBody = { a: '' }
|
||||||
|
for (let i = 0; i < 1010; i++) {
|
||||||
|
assertBody.a += 'aaaaaaaaaa'
|
||||||
|
}
|
||||||
|
reqBody = null
|
||||||
|
let body = await client.post('/json/slow', assertBody)
|
||||||
|
assert.deepEqual(body, { success: true })
|
||||||
|
assert.deepStrictEqual(reqBody, assertBody)
|
||||||
|
assert.strictEqual(log.info.callCount, 1)
|
||||||
|
assert.strictEqual(log.info.firstCall[0], 201)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should fail if body is too big', async function() {
|
||||||
|
reset()
|
||||||
|
|
||||||
|
const assertBody = { a: '' }
|
||||||
|
for (let i = 0; i < 10300; i++) {
|
||||||
|
assertBody.a += 'aaaaaaaaaa'
|
||||||
|
}
|
||||||
|
|
||||||
|
let err = await assert.isRejected(client.post('/json', assertBody))
|
||||||
|
|
||||||
|
assert.strictEqual(err.body.status, 413)
|
||||||
|
assert.ok(log.error.called)
|
||||||
|
assert.match(log.error.firstCall[0].message, /10240/)
|
||||||
|
assert.strictEqual(log.error.firstCall[0].status, 413)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should fail if not a valid json', async function() {
|
||||||
|
reset()
|
||||||
|
|
||||||
|
let err = await assert.isRejected(client.customRequest('POST', '/json', 'XXXXX'))
|
||||||
|
|
||||||
|
assert.strictEqual(err.body.status, 400)
|
||||||
|
assert.match(err.body.message, /invalid json/i)
|
||||||
|
assert.match(err.body.message, /token[^X]+X/i)
|
||||||
|
assert.strictEqual(err.body.request, 'XXXXX')
|
||||||
|
assert.strictEqual(log.error.callCount, 1)
|
||||||
|
assert.match(log.error.firstCall[0].message, /invalid json/i)
|
||||||
|
assert.match(log.error.firstCall[0].message, /token[^X]+X/i)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.test('should handle incomplete requests correctly', async function() {
|
||||||
|
reset()
|
||||||
|
|
||||||
|
let req = await client.customRequest('POST', '/json', 'aaaa', { returnRequest: true })
|
||||||
|
|
||||||
|
req.write('part1')
|
||||||
|
|
||||||
|
await setTimeout(20)
|
||||||
|
|
||||||
|
req.destroy()
|
||||||
|
|
||||||
|
await setTimeout(20)
|
||||||
|
|
||||||
|
assert.strictEqual(log.error.callCount, 0)
|
||||||
|
assert.strictEqual(log.info.callCount, 0)
|
||||||
|
// assert.strictEqual(log.info.firstCall[0].message, 'aborted')
|
||||||
|
})
|
||||||
|
})
|
|
@ -4,10 +4,10 @@ import fs from 'fs/promises'
|
||||||
import formidable from 'formidable'
|
import formidable from 'formidable'
|
||||||
import { Eltro as t, assert, stub } from 'eltro'
|
import { Eltro as t, assert, stub } from 'eltro'
|
||||||
import { setTimeout } from 'timers/promises'
|
import { setTimeout } from 'timers/promises'
|
||||||
import { Flaska, JsonHandler, FormidableHandler, FileResponse } from '../flaska.mjs'
|
import { Flaska, FormidableHandler, FileResponse } from '../flaska.mjs'
|
||||||
import Client from './client.mjs'
|
import Client from './client.mjs'
|
||||||
|
|
||||||
const port = 51024
|
const port = 51025
|
||||||
const log = {
|
const log = {
|
||||||
fatal: stub(),
|
fatal: stub(),
|
||||||
error: stub(),
|
error: stub(),
|
||||||
|
@ -20,7 +20,6 @@ const log = {
|
||||||
const flaska = new Flaska({ log })
|
const flaska = new Flaska({ log })
|
||||||
const client = new Client(port)
|
const client = new Client(port)
|
||||||
|
|
||||||
let reqBody = null
|
|
||||||
let file = null
|
let file = null
|
||||||
let uploaded = []
|
let uploaded = []
|
||||||
|
|
||||||
|
@ -32,16 +31,6 @@ flaska.after(function(ctx) {
|
||||||
flaska.get('/', function(ctx) {
|
flaska.get('/', function(ctx) {
|
||||||
ctx.body = { status: true }
|
ctx.body = { status: true }
|
||||||
})
|
})
|
||||||
flaska.post('/json', JsonHandler(), function(ctx) {
|
|
||||||
ctx.body = { success: true }
|
|
||||||
reqBody = ctx.req.body
|
|
||||||
})
|
|
||||||
flaska.post('/json/slow', JsonHandler(), async function(ctx) {
|
|
||||||
await setTimeout(300)
|
|
||||||
ctx.body = { success: true }
|
|
||||||
ctx.status = 201
|
|
||||||
reqBody = ctx.req.body
|
|
||||||
})
|
|
||||||
flaska.get('/timeout', function(ctx) {
|
flaska.get('/timeout', function(ctx) {
|
||||||
return new Promise(function() {})
|
return new Promise(function() {})
|
||||||
})
|
})
|
||||||
|
@ -60,6 +49,13 @@ flaska.post('/file/upload', FormidableHandler(formidable, {
|
||||||
uploaded.push(ctx.req.file)
|
uploaded.push(ctx.req.file)
|
||||||
ctx.body = ctx.req.file
|
ctx.body = ctx.req.file
|
||||||
})
|
})
|
||||||
|
flaska.post('/file/upload/many', FormidableHandler(formidable, {
|
||||||
|
uploadDir: './test/upload',
|
||||||
|
}), function(ctx) {
|
||||||
|
uploaded.push(ctx.req.files.herp)
|
||||||
|
uploaded.push(ctx.req.files.derp)
|
||||||
|
ctx.body = ctx.req.files
|
||||||
|
})
|
||||||
flaska.get('/file/leak', function(ctx) {
|
flaska.get('/file/leak', function(ctx) {
|
||||||
file = fsSync.createReadStream('./test/test.png')
|
file = fsSync.createReadStream('./test/test.png')
|
||||||
ctx.body = file
|
ctx.body = file
|
||||||
|
@ -89,90 +85,11 @@ t.describe('/', function() {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.describe('/json', function() {
|
|
||||||
t.beforeEach(function() {
|
|
||||||
log.info.reset()
|
|
||||||
})
|
|
||||||
t.test('should return success and store body', async function() {
|
|
||||||
const assertBody = { a: '' }
|
|
||||||
for (let i = 0; i < 1010; i++) {
|
|
||||||
assertBody.a += 'aaaaaaaaaa'
|
|
||||||
}
|
|
||||||
reqBody = null
|
|
||||||
let body = await client.post('/json', assertBody)
|
|
||||||
assert.deepEqual(body, { success: true })
|
|
||||||
assert.deepStrictEqual(reqBody, assertBody)
|
|
||||||
assert.strictEqual(log.info.callCount, 1)
|
|
||||||
assert.strictEqual(log.info.firstCall[0], 200)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.test('should return and log correctly', async function() {
|
|
||||||
const assertBody = { a: '' }
|
|
||||||
for (let i = 0; i < 1010; i++) {
|
|
||||||
assertBody.a += 'aaaaaaaaaa'
|
|
||||||
}
|
|
||||||
reqBody = null
|
|
||||||
let body = await client.post('/json/slow', assertBody)
|
|
||||||
assert.deepEqual(body, { success: true })
|
|
||||||
assert.deepStrictEqual(reqBody, assertBody)
|
|
||||||
assert.strictEqual(log.info.callCount, 1)
|
|
||||||
assert.strictEqual(log.info.firstCall[0], 201)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.test('should fail if body is too big', async function() {
|
|
||||||
reset()
|
|
||||||
|
|
||||||
const assertBody = { a: '' }
|
|
||||||
for (let i = 0; i < 10300; i++) {
|
|
||||||
assertBody.a += 'aaaaaaaaaa'
|
|
||||||
}
|
|
||||||
|
|
||||||
let err = await assert.isRejected(client.post('/json', assertBody))
|
|
||||||
|
|
||||||
assert.strictEqual(err.body.status, 413)
|
|
||||||
assert.ok(log.error.called)
|
|
||||||
assert.match(log.error.firstCall[0].message, /10240/)
|
|
||||||
assert.strictEqual(log.error.firstCall[0].status, 413)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.test('should fail if not a valid json', async function() {
|
|
||||||
reset()
|
|
||||||
|
|
||||||
let err = await assert.isRejected(client.customRequest('POST', '/json', 'aaaa'))
|
|
||||||
|
|
||||||
assert.strictEqual(err.body.status, 400)
|
|
||||||
assert.match(err.body.message, /invalid json/i)
|
|
||||||
assert.match(err.body.message, /token a/i)
|
|
||||||
assert.strictEqual(err.body.request, 'aaaa')
|
|
||||||
assert.strictEqual(log.error.callCount, 1)
|
|
||||||
assert.match(log.error.firstCall[0].message, /invalid json/i)
|
|
||||||
assert.match(log.error.firstCall[0].message, /token a/i)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.test('should handle incomplete requests correctly', async function() {
|
|
||||||
reset()
|
|
||||||
|
|
||||||
let req = await client.customRequest('POST', '/json', 'aaaa', { returnRequest: true })
|
|
||||||
|
|
||||||
req.write('part1')
|
|
||||||
|
|
||||||
await setTimeout(20)
|
|
||||||
|
|
||||||
req.destroy()
|
|
||||||
|
|
||||||
await setTimeout(20)
|
|
||||||
|
|
||||||
assert.strictEqual(log.error.callCount, 0)
|
|
||||||
assert.strictEqual(log.info.callCount, 0)
|
|
||||||
// assert.strictEqual(log.info.firstCall[0].message, 'aborted')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
t.describe('/timeout', function() {
|
t.describe('/timeout', function() {
|
||||||
t.test('server should handle timeout', async function() {
|
t.test('server should handle timeout', async function() {
|
||||||
reset()
|
reset()
|
||||||
|
|
||||||
let err = await assert.isRejected(client.customRequest('GET', '/timeout', JSON.stringify({}), { timeout: 20 }))
|
let err = await assert.isRejected(client.customRequest('GET', '/timeout', null, { timeout: 20 }))
|
||||||
|
|
||||||
assert.match(err.message, /timed out/)
|
assert.match(err.message, /timed out/)
|
||||||
assert.notOk(log.error.called)
|
assert.notOk(log.error.called)
|
||||||
|
@ -305,6 +222,82 @@ t.describe('/file/upload', function() {
|
||||||
|
|
||||||
assert.strictEqual(statSource.size, statTarget.size)
|
assert.strictEqual(statSource.size, statTarget.size)
|
||||||
assert.strictEqual(statSource.size, res.size)
|
assert.strictEqual(statSource.size, res.size)
|
||||||
|
assert.strictEqual(res.type, 'image/png')
|
||||||
|
})
|
||||||
|
t.test('server should have correct type', async function() {
|
||||||
|
let res = await client.upload('/file/upload', './test/test.jpg')
|
||||||
|
|
||||||
|
let [statSource, statTarget] = await Promise.all([
|
||||||
|
fs.stat('./test/test.jpg'),
|
||||||
|
fs.stat(res.path),
|
||||||
|
])
|
||||||
|
|
||||||
|
assert.strictEqual(statSource.size, statTarget.size)
|
||||||
|
assert.strictEqual(statSource.size, res.size)
|
||||||
|
assert.strictEqual(res.type, 'image/jpeg')
|
||||||
|
})
|
||||||
|
t.test('server should use type from user', async function() {
|
||||||
|
const assertType = 'some/test/here'
|
||||||
|
let res = await client.upload('/file/upload', './test/test.jpg', 'POST', {}, assertType)
|
||||||
|
|
||||||
|
let [statSource, statTarget] = await Promise.all([
|
||||||
|
fs.stat('./test/test.jpg'),
|
||||||
|
fs.stat(res.path),
|
||||||
|
])
|
||||||
|
|
||||||
|
assert.strictEqual(statSource.size, statTarget.size)
|
||||||
|
assert.strictEqual(statSource.size, res.size)
|
||||||
|
assert.strictEqual(res.type, assertType)
|
||||||
|
})
|
||||||
|
t.test('server should attempt to correct type if type is application/octet-stream', async function() {
|
||||||
|
const assertNotType = 'application/octet-stream'
|
||||||
|
let res = await client.upload('/file/upload', './test/test.jpg', 'POST', {}, assertNotType)
|
||||||
|
|
||||||
|
let [statSource, statTarget] = await Promise.all([
|
||||||
|
fs.stat('./test/test.jpg'),
|
||||||
|
fs.stat(res.path),
|
||||||
|
])
|
||||||
|
|
||||||
|
assert.strictEqual(statSource.size, statTarget.size)
|
||||||
|
assert.strictEqual(statSource.size, res.size)
|
||||||
|
assert.notStrictEqual(res.type, assertNotType)
|
||||||
|
assert.strictEqual(res.type, 'image/jpeg')
|
||||||
|
})
|
||||||
|
t.test('server fall back to type application/octet-stream if unknown', async function() {
|
||||||
|
let res = await client.upload('/file/upload', './test/test.gibberish')
|
||||||
|
|
||||||
|
let [statSource, statTarget] = await Promise.all([
|
||||||
|
fs.stat('./test/test.gibberish'),
|
||||||
|
fs.stat(res.path),
|
||||||
|
])
|
||||||
|
|
||||||
|
assert.strictEqual(statSource.size, statTarget.size)
|
||||||
|
assert.strictEqual(statSource.size, res.size)
|
||||||
|
assert.strictEqual(res.type, 'application/octet-stream')
|
||||||
|
|
||||||
|
res = await client.upload('/file/upload', './test/test.gibberish', 'POST', {}, 'application/octet-stream')
|
||||||
|
assert.strictEqual(res.type, 'application/octet-stream')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.describe('/file/upload/many', function() {
|
||||||
|
t.test('server should upload file', async function() {
|
||||||
|
let res = await client.upload('/file/upload/many', {
|
||||||
|
herp: './test/test.jpg',
|
||||||
|
derp: './test/test.png',
|
||||||
|
})
|
||||||
|
|
||||||
|
let [statSourcePng, statSourceJpg, statTargetHerp, statTargetDerp] = await Promise.all([
|
||||||
|
fs.stat('./test/test.png'),
|
||||||
|
fs.stat('./test/test.jpg'),
|
||||||
|
fs.stat(res.herp.path),
|
||||||
|
fs.stat(res.derp.path),
|
||||||
|
])
|
||||||
|
|
||||||
|
assert.strictEqual(statSourceJpg.size, statTargetHerp.size)
|
||||||
|
assert.strictEqual(statSourceJpg.size, res.herp.size)
|
||||||
|
assert.strictEqual(statSourcePng.size, statTargetDerp.size)
|
||||||
|
assert.strictEqual(statSourcePng.size, res.derp.size)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -253,6 +253,27 @@ t.describe('#CorsHandler()', function() {
|
||||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||||
assert.strictEqual(ctx.status, 204)
|
assert.strictEqual(ctx.status, 204)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.test('should set headers if allowedOrigins has a *', function() {
|
||||||
|
const assertOrigin = 'http://my.site.here'
|
||||||
|
|
||||||
|
corsHandler = CorsHandler({
|
||||||
|
allowedOrigins: ['*'],
|
||||||
|
})
|
||||||
|
ctx.req.headers['origin'] = assertOrigin
|
||||||
|
ctx.req.headers['access-control-request-method'] = 'GET'
|
||||||
|
|
||||||
|
assert.notOk(ctx.headers['Access-Control-Allow-Origin'])
|
||||||
|
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||||
|
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||||
|
|
||||||
|
corsHandler(ctx)
|
||||||
|
|
||||||
|
assert.strictEqual(ctx.headers['Vary'], 'Origin')
|
||||||
|
assert.strictEqual(ctx.headers['Access-Control-Allow-Origin'], assertOrigin)
|
||||||
|
assert.ok(ctx.headers['Access-Control-Allow-Methods'])
|
||||||
|
assert.strictEqual(ctx.status, 204)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.describe('GET/POST/DELETE/PATCH/PUT', function() {
|
t.describe('GET/POST/DELETE/PATCH/PUT', function() {
|
||||||
|
@ -498,8 +519,8 @@ t.describe('#JsonHandler()', function() {
|
||||||
finished = true
|
finished = true
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx.req.on.firstCall[1](Buffer.alloc(10, 'a'))
|
ctx.req.on.firstCall[1](Buffer.alloc(10, 'X'))
|
||||||
ctx.req.on.firstCall[1](Buffer.alloc(10, 'a'))
|
ctx.req.on.firstCall[1](Buffer.alloc(10, 'X'))
|
||||||
ctx.req.on.secondCall[1]()
|
ctx.req.on.secondCall[1]()
|
||||||
|
|
||||||
await setTimeout(10)
|
await setTimeout(10)
|
||||||
|
@ -510,11 +531,11 @@ t.describe('#JsonHandler()', function() {
|
||||||
assert.ok(err instanceof HttpError)
|
assert.ok(err instanceof HttpError)
|
||||||
assert.strictEqual(err.status, 400)
|
assert.strictEqual(err.status, 400)
|
||||||
assert.match(err.message, /JSON/)
|
assert.match(err.message, /JSON/)
|
||||||
assert.match(err.message, /Unexpected token a in/i)
|
assert.match(err.message, /Unexpected token[^X]+X/i)
|
||||||
assert.strictEqual(err.body.status, 400)
|
assert.strictEqual(err.body.status, 400)
|
||||||
assert.match(err.body.message, /Invalid JSON/i)
|
assert.match(err.body.message, /Invalid JSON/i)
|
||||||
assert.match(err.body.message, /Unexpected token a in/i)
|
assert.match(err.body.message, /Unexpected token[^X]+X/i)
|
||||||
assert.strictEqual(err.body.request, 'aaaaaaaaaaaaaaaaaaaa')
|
assert.strictEqual(err.body.request, 'XXXXXXXXXXXXXXXXXXXX')
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('should not throw if body is empty', async function() {
|
t.test('should not throw if body is empty', async function() {
|
||||||
|
@ -615,7 +636,10 @@ t.describe('#FormidableHandler()', function() {
|
||||||
|
|
||||||
let err = await assert.isRejected(handler(ctx))
|
let err = await assert.isRejected(handler(ctx))
|
||||||
|
|
||||||
assert.strictEqual(err, assertError)
|
assert.notStrictEqual(err, assertError)
|
||||||
|
assert.ok(err instanceof HttpError)
|
||||||
|
assert.strictEqual(err.message, assertError.message)
|
||||||
|
assert.strictEqual(err.status, 400)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('should throw rename error if rename fails', async function() {
|
t.test('should throw rename error if rename fails', async function() {
|
||||||
|
|
|
@ -45,8 +45,21 @@ t.describe('#addRoute()', function() {
|
||||||
router.addRoute('/:test/bla', function() {})
|
router.addRoute('/:test/bla', function() {})
|
||||||
router.addRoute('/bla/bla', function() {})
|
router.addRoute('/bla/bla', function() {})
|
||||||
router.addRoute('/bla/bla/bla', function() {})
|
router.addRoute('/bla/bla/bla', function() {})
|
||||||
assert.throws(function() { router.addRoute('/:asdf/', function() {}) }, /param/)
|
assert.throws(function() { router.addRoute('/:asdf', function() {}) }, /existing/)
|
||||||
assert.throws(function() { router.addRoute('/:test/asdf/:foobar', function() {}) }, /param/)
|
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() {})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('fail if adding multiple fullparam', function() {
|
t.test('fail if adding multiple fullparam', function() {
|
||||||
|
@ -56,10 +69,8 @@ t.describe('#addRoute()', function() {
|
||||||
router.addRoute('/:test/bla', function() {})
|
router.addRoute('/:test/bla', function() {})
|
||||||
router.addRoute('/:test/::bla', function() {})
|
router.addRoute('/:test/::bla', function() {})
|
||||||
router.addRoute('/bla/bla/bla', function() {})
|
router.addRoute('/bla/bla/bla', function() {})
|
||||||
assert.throws(function() { router.addRoute('/:test/asdf/::bla/fail', function() {}) }, /full.+param/)
|
assert.throws(function() { router.addRoute('/:test/asdf/::bla', function() {}) }, /existing/)
|
||||||
assert.throws(function() { router.addRoute('/:test/::bla/test', function() {}) }, /full.+param/)
|
assert.throws(function() { router.addRoute('/:test/::bla', function() {}) }, /existing/)
|
||||||
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() {
|
t.test('add route correctly', function() {
|
||||||
|
@ -67,39 +78,7 @@ t.describe('#addRoute()', function() {
|
||||||
let router = new FlaskaRouter()
|
let router = new FlaskaRouter()
|
||||||
router.addRoute('/a/b/c', assertHandler)
|
router.addRoute('/a/b/c', assertHandler)
|
||||||
let result = router.match('/a/b/c')
|
let result = router.match('/a/b/c')
|
||||||
assert.strictEqual(result.handler, assertHandler)
|
assert.strictEqual(result.path.handlers[0], 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() {
|
t.test('support single middlewares correctly', function() {
|
||||||
|
@ -107,10 +86,10 @@ t.describe('#addRoute()', function() {
|
||||||
let assertMiddleware = function() { return 1 }
|
let assertMiddleware = function() { return 1 }
|
||||||
let router = new FlaskaRouter()
|
let router = new FlaskaRouter()
|
||||||
router.addRoute('/a', assertMiddleware, assertHandler)
|
router.addRoute('/a', assertMiddleware, assertHandler)
|
||||||
assert.ok(router.root.children.get('a'))
|
let result = router.match('/a')
|
||||||
assert.strictEqual(router.root.children.get('a').handler, assertHandler)
|
assert.strictEqual(result.path.handlers.length, 2)
|
||||||
assert.strictEqual(router.root.children.get('a').middlewares.length, 1)
|
assert.strictEqual(result.path.handlers[0], assertMiddleware)
|
||||||
assert.strictEqual(router.root.children.get('a').middlewares[0], assertMiddleware)
|
assert.strictEqual(result.path.handlers[1], assertHandler)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('support multi middlewares correctly', function() {
|
t.test('support multi middlewares correctly', function() {
|
||||||
|
@ -119,15 +98,15 @@ t.describe('#addRoute()', function() {
|
||||||
let router = new FlaskaRouter()
|
let router = new FlaskaRouter()
|
||||||
router.addRoute('/a', [assertMiddleware], assertHandler)
|
router.addRoute('/a', [assertMiddleware], assertHandler)
|
||||||
router.addRoute('/b', [assertMiddleware, assertMiddleware], assertHandler)
|
router.addRoute('/b', [assertMiddleware, assertMiddleware], assertHandler)
|
||||||
assert.ok(router.root.children.get('a'))
|
let resultA = router.match('/a')
|
||||||
assert.strictEqual(router.root.children.get('a').handler, assertHandler)
|
assert.strictEqual(resultA.path.handlers.length, 2)
|
||||||
assert.strictEqual(router.root.children.get('a').middlewares.length, 1)
|
assert.strictEqual(resultA.path.handlers[0], assertMiddleware)
|
||||||
assert.strictEqual(router.root.children.get('a').middlewares[0], assertMiddleware)
|
assert.strictEqual(resultA.path.handlers[1], assertHandler)
|
||||||
assert.ok(router.root.children.get('b'))
|
let resultB = router.match('/b')
|
||||||
assert.strictEqual(router.root.children.get('b').handler, assertHandler)
|
assert.strictEqual(resultB.path.handlers.length, 3)
|
||||||
assert.strictEqual(router.root.children.get('b').middlewares.length, 2)
|
assert.strictEqual(resultB.path.handlers[0], assertMiddleware)
|
||||||
assert.strictEqual(router.root.children.get('b').middlewares[0], assertMiddleware)
|
assert.strictEqual(resultB.path.handlers[1], assertMiddleware)
|
||||||
assert.strictEqual(router.root.children.get('b').middlewares[1], assertMiddleware)
|
assert.strictEqual(resultB.path.handlers[2], assertHandler)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -136,43 +115,20 @@ t.describe('#match()', function() {
|
||||||
let assertMatched = false
|
let assertMatched = false
|
||||||
let router = new FlaskaRouter()
|
let router = new FlaskaRouter()
|
||||||
router.addRoute('/test', function() { assertMatched = true })
|
router.addRoute('/test', function() { assertMatched = true })
|
||||||
let result = router.match('/test')
|
let result = router.match('/test').path
|
||||||
assert.ok(result.handler)
|
assert.strictEqual(result.handlers.length, 1)
|
||||||
assert.ok(result.middlewares)
|
result.handlers[0]()
|
||||||
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)
|
assert.strictEqual(assertMatched, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.test('return middlewares in handlers', function() {
|
||||||
t.test('return middlewares', function() {
|
|
||||||
let assertMatched = false
|
let assertMatched = false
|
||||||
let assertMiddleware = function() { assertMatched = true }
|
let assertMiddleware = function() { assertMatched = true }
|
||||||
let router = new FlaskaRouter()
|
let router = new FlaskaRouter()
|
||||||
router.addRoute('/test', assertMiddleware, function() { })
|
router.addRoute('/test', assertMiddleware, function() { })
|
||||||
let result = router.match('/test')
|
let result = router.match('/test').path
|
||||||
assert.ok(result.handler)
|
assert.strictEqual(result.handlers.length, 2)
|
||||||
assert.ok(result.middlewares)
|
result.handlers[0]()
|
||||||
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)
|
assert.strictEqual(assertMatched, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -182,20 +138,8 @@ t.describe('#match()', function() {
|
||||||
let router = new FlaskaRouter()
|
let router = new FlaskaRouter()
|
||||||
router.addRoute('/test/:id', function() { assertMatched = true })
|
router.addRoute('/test/:id', function() { assertMatched = true })
|
||||||
let result = router.match('/test/' + assertParameter)
|
let result = router.match('/test/' + assertParameter)
|
||||||
assert.ok(result.handler)
|
assert.strictEqual(result.path.handlers.length, 1)
|
||||||
assert.ok(result.middlewares)
|
result.path.handlers[0]()
|
||||||
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(assertMatched, true)
|
||||||
assert.strictEqual(result.params.id, assertParameter)
|
assert.strictEqual(result.params.id, assertParameter)
|
||||||
})
|
})
|
||||||
|
@ -206,20 +150,8 @@ t.describe('#match()', function() {
|
||||||
let router = new FlaskaRouter()
|
let router = new FlaskaRouter()
|
||||||
router.addRoute('/test/::id', function() { assertMatched = true })
|
router.addRoute('/test/::id', function() { assertMatched = true })
|
||||||
let result = router.match('/test/' + assertParameter)
|
let result = router.match('/test/' + assertParameter)
|
||||||
assert.ok(result.handler)
|
assert.strictEqual(result.path.handlers.length, 1)
|
||||||
assert.ok(result.middlewares)
|
result.path.handlers[0]()
|
||||||
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(assertMatched, true)
|
||||||
assert.strictEqual(result.params.id, assertParameter)
|
assert.strictEqual(result.params.id, assertParameter)
|
||||||
})
|
})
|
||||||
|
@ -231,15 +163,13 @@ t.describe('#match()', function() {
|
||||||
router.addRoute('/test/:bla', assertParamFunc)
|
router.addRoute('/test/:bla', assertParamFunc)
|
||||||
router.addRoute('/::id', assertFullFunc)
|
router.addRoute('/::id', assertFullFunc)
|
||||||
let result = router.match('/test/123')
|
let result = router.match('/test/123')
|
||||||
assert.strictEqual(result.handler, assertParamFunc)
|
assert.strictEqual(result.path.handlers.length, 1)
|
||||||
assert.ok(result.middlewares)
|
assert.strictEqual(result.path.handlers[0], assertParamFunc)
|
||||||
assert.strictEqual(result.middlewares.length, 0)
|
|
||||||
assert.strictEqual(result.params.bla, '123')
|
assert.strictEqual(result.params.bla, '123')
|
||||||
|
|
||||||
result = router.match('/test/123/asdf')
|
result = router.match('/test/123/asdf')
|
||||||
assert.strictEqual(result.handler, assertFullFunc)
|
assert.strictEqual(result.path.handlers.length, 1)
|
||||||
assert.ok(result.middlewares)
|
assert.strictEqual(result.path.handlers[0], assertFullFunc)
|
||||||
assert.strictEqual(result.middlewares.length, 0)
|
|
||||||
assert.strictEqual(result.params.id, 'test/123/asdf')
|
assert.strictEqual(result.params.id, 'test/123/asdf')
|
||||||
assert.notOk(result.params.bla)
|
assert.notOk(result.params.bla)
|
||||||
})
|
})
|
||||||
|
@ -250,19 +180,8 @@ t.describe('#match()', function() {
|
||||||
router.addRoute('/test/:id', function() { assertMatched = false })
|
router.addRoute('/test/:id', function() { assertMatched = false })
|
||||||
router.addRoute('/test/:id/test1', function() { })
|
router.addRoute('/test/:id/test1', function() { })
|
||||||
let result = router.match('/test/asdf/test1')
|
let result = router.match('/test/asdf/test1')
|
||||||
assert.ok(result.handler)
|
assert.strictEqual(result.path.handlers.length, 1)
|
||||||
assert.ok(result.middlewares)
|
result.path.handlers[0]()
|
||||||
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(assertMatched, true)
|
||||||
assert.strictEqual(result.params.id, 'asdf')
|
assert.strictEqual(result.params.id, 'asdf')
|
||||||
})
|
})
|
||||||
|
@ -277,24 +196,25 @@ t.describe('#match()', function() {
|
||||||
router.addRoute('/foo/::path', assertFunction)
|
router.addRoute('/foo/::path', assertFunction)
|
||||||
router.addRoute('/::path', assertFailFunction)
|
router.addRoute('/::path', assertFailFunction)
|
||||||
|
|
||||||
assert.strictEqual(router.match('/test/123').handler, assertFunction)
|
assert.strictEqual(router.match('/test/123').path.handlers[0], assertFunction)
|
||||||
assert.strictEqual(router.match('/test/asdfasdg').handler, assertFunction)
|
assert.strictEqual(router.match('/test/asdfasdg').path.handlers[0], assertFunction)
|
||||||
assert.strictEqual(router.match('/test/test/sdafsda').handler, assertFunction)
|
assert.strictEqual(router.match('/test/test/sdafsda').path.handlers[0], assertFunction)
|
||||||
assert.strictEqual(router.match('/test/test/sdafsda/gdfshe4/43y34/wtaw').handler, assertFunction)
|
assert.strictEqual(router.match('/test/test/sdafsda/gdfshe4/43y34/wtaw').path.handlers[0], assertFunction)
|
||||||
assert.strictEqual(router.match('/foo/123').handler, assertFunction)
|
assert.strictEqual(router.match('/foo/123').path.handlers[0], assertFunction)
|
||||||
assert.strictEqual(router.match('/foo/bar/baz/test').handler, assertFunction)
|
assert.strictEqual(router.match('/foo/bar/baz/test').path.handlers[0], assertFunction)
|
||||||
assert.ok(router.match('/test/123/yweherher/reher/h34h34/'))
|
assert.ok(router.match('/test/123/yweherher/reher/h34h34/'))
|
||||||
assert.strictEqual(router.match('/test/123/yweherher/reher/h34h34/').handler, assertFailFunction)
|
assert.strictEqual(router.match('/test/123/yweherher/reher/h34h34/').path.handlers[0], assertFailFunction)
|
||||||
assert.ok(router.match('/test/foo/bar'))
|
assert.ok(router.match('/test/foo/bar'))
|
||||||
assert.strictEqual(router.match('/test/foo/bar').handler, assertFailFunction)
|
assert.strictEqual(router.match('/test/foo/bar').path.handlers[0], assertFailFunction)
|
||||||
assert.ok(router.match('/'))
|
assert.ok(router.match('/'))
|
||||||
assert.strictEqual(router.match('/').handler, assertFailFunction)
|
assert.strictEqual(router.match('/').path.handlers[0], assertFailFunction)
|
||||||
assert.ok(router.match('/something/else/goes/here'))
|
assert.ok(router.match('/something/else/goes/here'))
|
||||||
assert.strictEqual(router.match('/something/else/goes/here').handler, assertFailFunction)
|
assert.strictEqual(router.match('/something/else/goes/here').path.handlers[0], assertFailFunction)
|
||||||
|
|
||||||
router.addRoute('/', assertRootFunction)
|
router.addRoute('/', assertRootFunction)
|
||||||
|
router.compile()
|
||||||
assert.ok(router.match('/'))
|
assert.ok(router.match('/'))
|
||||||
assert.strictEqual(router.match('/').handler, assertRootFunction)
|
assert.strictEqual(router.match('/').path.handlers[0], assertRootFunction)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.test('return null when no match is found', function() {
|
t.test('return null when no match is found', function() {
|
||||||
|
|
BIN
test/test.gibberish
Normal file
BIN
test/test.gibberish
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
BIN
test/test.jpg
Normal file
BIN
test/test.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
Loading…
Reference in a new issue