@srfnstack/spliffy 0.5.5 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +0 -0
- package/README.md +0 -0
- package/package.json +14 -6
- package/src/content-types.mjs +75 -0
- package/src/content.mjs +95 -0
- package/src/decorator.mjs +205 -0
- package/src/handler.mjs +240 -0
- package/src/httpStatusCodes.mjs +103 -0
- package/src/{index.js → index.mjs} +14 -18
- package/src/log.mjs +59 -0
- package/src/middleware.mjs +90 -0
- package/src/nodeModuleHandler.mjs +61 -0
- package/src/routes.mjs +180 -0
- package/src/server.mjs +126 -0
- package/src/serverConfig.mjs +84 -0
- package/src/start.mjs +25 -0
- package/src/staticHandler.mjs +74 -0
- package/src/url.mjs +24 -0
- package/src/content-types.js +0 -75
- package/src/content.js +0 -56
- package/src/dispatcher.js +0 -190
- package/src/expressShim.js +0 -120
- package/src/log.js +0 -43
- package/src/middleware.js +0 -121
- package/src/nodeModuleHandler.js +0 -54
- package/src/parseUrl.js +0 -43
- package/src/routeUtil.js +0 -67
- package/src/routes.js +0 -191
- package/src/secure.js +0 -53
- package/src/serverConfig.js +0 -70
- package/src/start.js +0 -39
- package/src/staticHandler.js +0 -115
package/src/log.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
const inspect = require( 'util' ).inspect
|
|
2
|
-
const levelOrder = { DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3 }
|
|
3
|
-
let logAccess = true
|
|
4
|
-
let logLevel = levelOrder.INFO
|
|
5
|
-
|
|
6
|
-
const ifLevelEnabled = ( fn, level, args ) => {
|
|
7
|
-
const configLevel = levelOrder[logLevel] || levelOrder.INFO
|
|
8
|
-
if( !levelOrder[level] || levelOrder[level] >= configLevel ) {
|
|
9
|
-
fn( `[${new Date().toISOString()}] [${level}] ${args.map( a => typeof a === 'string' ? a : inspect( a, { depth: null } ) ).join( ' ' )}` )
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
module.exports = {
|
|
14
|
-
setLogAccess( enable ) {
|
|
15
|
-
logAccess = !!enable
|
|
16
|
-
},
|
|
17
|
-
setLogLevel( level ) {
|
|
18
|
-
if( !levelOrder.hasOwnProperty( level ) ) {
|
|
19
|
-
throw `Invalid level: ${level}`
|
|
20
|
-
}
|
|
21
|
-
logLevel = level
|
|
22
|
-
},
|
|
23
|
-
warning( e ) {
|
|
24
|
-
ifLevelEnabled( console.warn, 'WARN', [...arguments] )
|
|
25
|
-
},
|
|
26
|
-
warn( e ) {
|
|
27
|
-
ifLevelEnabled( console.warn, 'WARN', [...arguments] )
|
|
28
|
-
},
|
|
29
|
-
info( e ) {
|
|
30
|
-
ifLevelEnabled( console.info, 'INFO', [...arguments] )
|
|
31
|
-
},
|
|
32
|
-
gne( e ) {
|
|
33
|
-
ifLevelEnabled( console.info, 'GOOD NEWS EVERYONE!', [...arguments] )
|
|
34
|
-
},
|
|
35
|
-
access( e ) {
|
|
36
|
-
if( logAccess ) {
|
|
37
|
-
ifLevelEnabled( console.info, 'ACCESS', [...arguments] )
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
error( e ) {
|
|
41
|
-
ifLevelEnabled( console.error, 'ERROR', [...arguments].map( arg => arg.stack ? arg.stack : arg ) )
|
|
42
|
-
}
|
|
43
|
-
}
|
package/src/middleware.js
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
const log = require( './log' )
|
|
2
|
-
const uuid = require( 'uuid' ).v4
|
|
3
|
-
/**
|
|
4
|
-
* middleware is stored as an object where the properties are request methods the middleware applies to
|
|
5
|
-
* if a middleware applies to all methods, the property ALL is used
|
|
6
|
-
* example:
|
|
7
|
-
* {
|
|
8
|
-
* GET: [(req,res,next)=>console.log('ice cream man')]
|
|
9
|
-
* POST: [(req,res,next)=>console.log('gelato')]
|
|
10
|
-
* ALL: [(req,res,next)=>console.log('bruce banner')]
|
|
11
|
-
* }
|
|
12
|
-
*/
|
|
13
|
-
const mergeMiddleware = ( incoming, existing ) => {
|
|
14
|
-
const mergeInto = cloneMiddleware( existing )
|
|
15
|
-
|
|
16
|
-
validateMiddleware( incoming )
|
|
17
|
-
if( Array.isArray( incoming ) ) {
|
|
18
|
-
mergeInto.ALL = ( existing.ALL || [] ).concat( incoming )
|
|
19
|
-
} else if( typeof incoming === 'object' ) {
|
|
20
|
-
for( let method in incoming ) {
|
|
21
|
-
let upMethod = method.toUpperCase()
|
|
22
|
-
mergeInto[upMethod] = ( mergeInto[method] || [] ).concat( incoming[upMethod] || [] )
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return mergeInto
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const cloneMiddleware = ( middleware ) => {
|
|
29
|
-
const clone = { ...middleware }
|
|
30
|
-
for( let method in middleware ) {
|
|
31
|
-
clone[method] = [...( middleware[method] || [] )]
|
|
32
|
-
}
|
|
33
|
-
return clone
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Ensure the given middleware is valid
|
|
38
|
-
* @param middleware
|
|
39
|
-
*/
|
|
40
|
-
const validateMiddleware = ( middleware ) => {
|
|
41
|
-
if( Array.isArray( middleware ) ) {
|
|
42
|
-
validateMiddlewareArray( middleware )
|
|
43
|
-
} else if( typeof middleware === 'object' ) {
|
|
44
|
-
for( let method in middleware ) {
|
|
45
|
-
//ensure methods are always available as uppercase
|
|
46
|
-
let upMethod = method.toUpperCase()
|
|
47
|
-
middleware[upMethod] = middleware[method]
|
|
48
|
-
validateMiddlewareArray( middleware[upMethod] )
|
|
49
|
-
}
|
|
50
|
-
} else {
|
|
51
|
-
throw new Error( 'Invalid middleware definition: ' + middleware )
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const validateMiddlewareArray = ( arr ) => {
|
|
56
|
-
if( !Array.isArray( arr ) ) {
|
|
57
|
-
throw 'middleware must be an array of functions'
|
|
58
|
-
}
|
|
59
|
-
for(let f of arr){
|
|
60
|
-
if( typeof f !== 'function' ) {
|
|
61
|
-
throw 'Each element in the array of middleware must be a function'
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async function executeMiddleware( middlewarez, req, res, reqErr ) {
|
|
67
|
-
|
|
68
|
-
const applicableMiddleware = ( middlewarez.ALL || [] ).concat( middlewarez[req.method] || [] )
|
|
69
|
-
const errorMiddleware = applicableMiddleware.filter( mw => mw.length === 4 )
|
|
70
|
-
const normalMiddleware = applicableMiddleware.filter( mw => mw.length === 3 )
|
|
71
|
-
let err
|
|
72
|
-
await new Promise( ( resolve, reject ) => {
|
|
73
|
-
let current = -1
|
|
74
|
-
let isError = false
|
|
75
|
-
const next = ( mwErr ) => {
|
|
76
|
-
if( !isError && mwErr ) {
|
|
77
|
-
isError = true
|
|
78
|
-
current = -1
|
|
79
|
-
}
|
|
80
|
-
if( res.writableEnded ) {
|
|
81
|
-
resolve()
|
|
82
|
-
return
|
|
83
|
-
}
|
|
84
|
-
current++
|
|
85
|
-
if( ( isError && current === errorMiddleware.length ) ||
|
|
86
|
-
( !isError && current === normalMiddleware.length )
|
|
87
|
-
) {
|
|
88
|
-
if( mwErr )
|
|
89
|
-
reject( mwErr )
|
|
90
|
-
resolve()
|
|
91
|
-
} else {
|
|
92
|
-
try {
|
|
93
|
-
let mw = isError ? errorMiddleware[current] : normalMiddleware[current]
|
|
94
|
-
if( mwErr ) {
|
|
95
|
-
mw( mwErr, req, res, next )
|
|
96
|
-
} else {
|
|
97
|
-
mw( req, res, next )
|
|
98
|
-
}
|
|
99
|
-
} catch( e ) {
|
|
100
|
-
|
|
101
|
-
log.error( 'Middleware threw exception', e )
|
|
102
|
-
next( e )
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
next( reqErr )
|
|
108
|
-
} ).catch( e => {
|
|
109
|
-
err = e
|
|
110
|
-
return null
|
|
111
|
-
} )
|
|
112
|
-
if(err) throw err
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
module.exports = {
|
|
116
|
-
validateMiddlewareArray,
|
|
117
|
-
cloneMiddleware,
|
|
118
|
-
validateMiddleware,
|
|
119
|
-
mergeMiddleware,
|
|
120
|
-
executeMiddleware
|
|
121
|
-
}
|
package/src/nodeModuleHandler.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
const fs = require( 'fs' )
|
|
2
|
-
const path = require( 'path' )
|
|
3
|
-
const serverConfig = require( './serverConfig' )
|
|
4
|
-
const { mergeMiddleware } = require( "./middleware" );
|
|
5
|
-
const { addStaticRoute } = require( "./routeUtil" );
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const stripLeadingSlash = p => p.startsWith( '/' ) ? p.substr( 1 ) : p
|
|
9
|
-
|
|
10
|
-
module.exports = {
|
|
11
|
-
/**
|
|
12
|
-
This method will add all of the configured node_module files to the given routes.
|
|
13
|
-
The configured node moduleRoutes must be explicit files, no pattern matching is supported.
|
|
14
|
-
Generating the list of files using pattern matching yourself is highly discouraged.
|
|
15
|
-
It is much safer to explicitly list every file you wish to be served so you don't inadvertently serve additional files.
|
|
16
|
-
*/
|
|
17
|
-
async addNodeModuleRoutes( routes ) {
|
|
18
|
-
let nodeModuleRoutes = serverConfig.current.nodeModuleRoutes;
|
|
19
|
-
if( nodeModuleRoutes && typeof nodeModuleRoutes === 'object' ) {
|
|
20
|
-
const nodeModulesDir = nodeModuleRoutes.nodeModulesPath ? path.resolve( nodeModuleRoutes.nodeModulesPath ) : path.resolve( serverConfig.current.routeDir, '..', 'node_modules' )
|
|
21
|
-
if( !fs.existsSync( nodeModulesDir ) ) {
|
|
22
|
-
throw new Error( `Unable to find node_modules dir at ${nodeModulesDir}` )
|
|
23
|
-
}
|
|
24
|
-
let prefix = nodeModuleRoutes.routePrefix || 'lib'
|
|
25
|
-
if( !Array.isArray( nodeModuleRoutes.files ) ) {
|
|
26
|
-
nodeModuleRoutes.files = [nodeModuleRoutes.files]
|
|
27
|
-
}
|
|
28
|
-
for( let file of nodeModuleRoutes.files ) {
|
|
29
|
-
let filePath, urlPath
|
|
30
|
-
if( file && typeof file === 'object' ) {
|
|
31
|
-
filePath = path.join( nodeModulesDir, file.modulePath )
|
|
32
|
-
urlPath = `${prefix}/${stripLeadingSlash( file.urlPath || file.modulePath )}`
|
|
33
|
-
} else if( file && typeof file === 'string' ) {
|
|
34
|
-
filePath = path.join( nodeModulesDir, file )
|
|
35
|
-
urlPath = `${prefix}/${stripLeadingSlash( file )}`
|
|
36
|
-
} else {
|
|
37
|
-
throw new Error( 'Invalid node_module file: ' + file )
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if( fs.existsSync( filePath ) ) {
|
|
41
|
-
let parts = urlPath.split( '/' )
|
|
42
|
-
let lastPart = parts.pop()
|
|
43
|
-
let parent = parts.reduce( ( rts, part ) => rts[part] || ( rts[part] = {} ), routes )
|
|
44
|
-
let mw = {}
|
|
45
|
-
mergeMiddleware( serverConfig.current.middleware, mw )
|
|
46
|
-
mergeMiddleware( nodeModuleRoutes.middleware || {}, mw )
|
|
47
|
-
await addStaticRoute( parent, { name: lastPart }, filePath, mw )
|
|
48
|
-
} else {
|
|
49
|
-
console.warn( `The specified node_modules file: ${file} does not exist and will not be served.` )
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
package/src/parseUrl.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
const serverConfig = require( './serverConfig' )
|
|
2
|
-
|
|
3
|
-
module.exports = ( path, query ) => {
|
|
4
|
-
let parsed = { path: path, query: {} }
|
|
5
|
-
if( query ) {
|
|
6
|
-
if( serverConfig.current.decodeQueryParameters ) {
|
|
7
|
-
query = decodeURIComponent( query.replace( /\+/g, '%20' ) )
|
|
8
|
-
}
|
|
9
|
-
let key = ''
|
|
10
|
-
let value = ''
|
|
11
|
-
let isKey = true
|
|
12
|
-
|
|
13
|
-
for( let i = 0; i <= query.length; i++ ) {
|
|
14
|
-
if( i === query.length || query[i] === '&' ) {
|
|
15
|
-
//trailing or consecutive &
|
|
16
|
-
if( key === '' && value === '' ) continue
|
|
17
|
-
|
|
18
|
-
if( parsed.query[key] ) {
|
|
19
|
-
if( !Array.isArray( parsed.query[key] ) ) {
|
|
20
|
-
parsed.query[key] = [parsed.query[key]]
|
|
21
|
-
}
|
|
22
|
-
parsed.query[key].push( value )
|
|
23
|
-
} else {
|
|
24
|
-
parsed.query[key] = value
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
key = ''
|
|
28
|
-
value = ''
|
|
29
|
-
isKey = true
|
|
30
|
-
} else if( query[i] === '=' ) {
|
|
31
|
-
isKey = false
|
|
32
|
-
} else {
|
|
33
|
-
if( isKey ) {
|
|
34
|
-
key += query[i]
|
|
35
|
-
} else {
|
|
36
|
-
value += query[i]
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return parsed
|
|
43
|
-
}
|
package/src/routeUtil.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
const staticHandler = require( './staticHandler' )
|
|
2
|
-
const serverConfig = require( './serverConfig' )
|
|
3
|
-
const contentTypes = require( './content-types.js' )
|
|
4
|
-
|
|
5
|
-
const addRoute = ( routes, path, name, route ) => {
|
|
6
|
-
let routeData = getNewRouteData( name, routes )
|
|
7
|
-
if( routes[routeData.name] ) {
|
|
8
|
-
throw `Duplicate route name found for route file ${path}`
|
|
9
|
-
}
|
|
10
|
-
routes[routeData.name] = Object.assign( routeData.route, route )
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const addStaticRoute = async ( routes, f, path, inheritedMiddleware ) => {
|
|
14
|
-
const extension = f.name.indexOf( '.' ) > -1 ? f.name.slice( f.name.lastIndexOf( '.' ) ).toLowerCase() : 'default'
|
|
15
|
-
let contentType = serverConfig.current.staticContentTypes && serverConfig.current.staticContentTypes[extension] || null
|
|
16
|
-
|
|
17
|
-
contentType = contentType ? contentType : contentTypes[extension]
|
|
18
|
-
let route = {
|
|
19
|
-
handler: await staticHandler.create( path, contentType ),
|
|
20
|
-
static: true,
|
|
21
|
-
middleware: inheritedMiddleware
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
addRoute( routes, path, f.name, route )
|
|
25
|
-
for( let ext of serverConfig.current.resolveWithoutExtension ) {
|
|
26
|
-
if( f.name.endsWith( ext ) ) {
|
|
27
|
-
addRoute( routes, path, f.name.substr( 0, f.name.length - ext.length ), route )
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Build a route data with an initialized route object
|
|
35
|
-
* @param name The name of the route
|
|
36
|
-
* @param routes The current routes, used for validation
|
|
37
|
-
* @returns {{route: {}, name: *}|{route: {key: string}, name: string}|{route: {catchall: boolean}, name: string}}
|
|
38
|
-
*/
|
|
39
|
-
const getNewRouteData = ( name, routes ) => {
|
|
40
|
-
if( name.startsWith( '$' ) && name.indexOf( '.' ) === -1 ) {
|
|
41
|
-
if( 'variable' in routes ) {
|
|
42
|
-
throw `You can not have two path variables in the same dir. Conflicting handlers: ${name} and \$${routes.variable.key}.`
|
|
43
|
-
}
|
|
44
|
-
return {
|
|
45
|
-
name: 'variable',
|
|
46
|
-
route: {
|
|
47
|
-
key: name.substr( 1 )
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
} else if( name.endsWith( '+' ) ) {
|
|
51
|
-
return {
|
|
52
|
-
name: name.substr( 0, name.length - 1 ),
|
|
53
|
-
route: {
|
|
54
|
-
catchall: true
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
} else {
|
|
58
|
-
return { name, route: {} }
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
module.exports = {
|
|
64
|
-
getNewRouteData,
|
|
65
|
-
addStaticRoute,
|
|
66
|
-
addRoute
|
|
67
|
-
}
|
package/src/routes.js
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
const fs = require( 'fs' )
|
|
2
|
-
const path = require( 'path' )
|
|
3
|
-
const serverConfig = require( './serverConfig' )
|
|
4
|
-
const log = require( './log' )
|
|
5
|
-
const { validateMiddleware, mergeMiddleware } = require( "./middleware" );
|
|
6
|
-
const { getNewRouteData, addRoute, addStaticRoute } = require( "./routeUtil" );
|
|
7
|
-
const { addNodeModuleRoutes } = require( "./nodeModuleHandler" );
|
|
8
|
-
const state = {
|
|
9
|
-
routes: {},
|
|
10
|
-
initializing: false,
|
|
11
|
-
hasPendingFsEvent: false
|
|
12
|
-
}
|
|
13
|
-
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD', 'CONNECT', 'TRACE']
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Recursively walk the specified directory to discover all of the routes and middleware
|
|
17
|
-
* @param currentFile The file or directory to search for routes
|
|
18
|
-
* @param path The full path to the current file
|
|
19
|
-
* @param inheritedMiddleware Middleware that is inherited from the app or parent routes
|
|
20
|
-
* @returns {Promise<{} | Promise<(Promise<(Promise<T | never>|{})[] | never>|{})[] | never>>}
|
|
21
|
-
*/
|
|
22
|
-
const findRoutes = async ( currentFile, path, inheritedMiddleware ) => {
|
|
23
|
-
let routes = {}
|
|
24
|
-
if( currentFile.isDirectory() ) {
|
|
25
|
-
|
|
26
|
-
let routeData = getNewRouteData( currentFile.name, routes )
|
|
27
|
-
const files = fs.readdirSync( path, { withFileTypes: true } )
|
|
28
|
-
|
|
29
|
-
const routeMiddleware = files
|
|
30
|
-
.filter( f => f.name.endsWith( '.mw.js' ) )
|
|
31
|
-
.map( f => {
|
|
32
|
-
let exports = require( path + '/' + f.name )
|
|
33
|
-
if( !exports.middleware ) {
|
|
34
|
-
throw new Error( '.mw.js files must export a middleware property' )
|
|
35
|
-
}
|
|
36
|
-
validateMiddleware( exports.middleware )
|
|
37
|
-
return exports.middleware
|
|
38
|
-
} )
|
|
39
|
-
.reduce( ( result, incoming ) => mergeMiddleware( incoming, result ), inheritedMiddleware )
|
|
40
|
-
|
|
41
|
-
return Promise.all( files
|
|
42
|
-
.filter( f => !f.name.endsWith( '.mw.js' ) )
|
|
43
|
-
.map(
|
|
44
|
-
( f ) => findRoutes( f, path + '/' + f.name, routeMiddleware )
|
|
45
|
-
)
|
|
46
|
-
)
|
|
47
|
-
.then(
|
|
48
|
-
routes => ( {
|
|
49
|
-
[routeData.name]: routes.reduce(
|
|
50
|
-
( res, route ) => {
|
|
51
|
-
for( let name of Object.keys( route ) ) {
|
|
52
|
-
if( res[name] ) {
|
|
53
|
-
throw `Duplicate route name ${name} found in path: ${path}`
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return Object.assign( res, route )
|
|
57
|
-
},
|
|
58
|
-
routeData.route
|
|
59
|
-
)
|
|
60
|
-
} )
|
|
61
|
-
)
|
|
62
|
-
} else if( !serverConfig.current.staticMode && currentFile.name.endsWith( '.rt.js' ) ) {
|
|
63
|
-
addRoute( routes, path, currentFile.name.substr( 0, currentFile.name.length - '.rt.js'.length ), buildRoute( {}, path, inheritedMiddleware ) )
|
|
64
|
-
} else {
|
|
65
|
-
await addStaticRoute( routes, currentFile, path, inheritedMiddleware )
|
|
66
|
-
}
|
|
67
|
-
return routes
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* load and gather data about the specified route handler file
|
|
73
|
-
* @param route The initialized route data
|
|
74
|
-
* @param path The full path to the file
|
|
75
|
-
* @param inheritedMiddleware The inherited middleware
|
|
76
|
-
* @returns {*}
|
|
77
|
-
*/
|
|
78
|
-
const buildRoute = ( route, path, inheritedMiddleware ) => {
|
|
79
|
-
let handlers = require( path )
|
|
80
|
-
|
|
81
|
-
route.handler = {}
|
|
82
|
-
route.middleware = mergeMiddleware( handlers.middleware || [], inheritedMiddleware )
|
|
83
|
-
//remove the middleware property if it was set so validation can still pass and to ensure we don't make bogus handlers
|
|
84
|
-
delete handlers.middleware
|
|
85
|
-
for( let method in handlers ) {
|
|
86
|
-
if( HTTP_METHODS.indexOf( method ) === -1 ) {
|
|
87
|
-
throw `Method: ${method} in file ${path} is not a valid http method. It must be one of: ${HTTP_METHODS}. Method names must be all uppercase.`
|
|
88
|
-
}
|
|
89
|
-
let handler = handlers[method]
|
|
90
|
-
if( typeof handler !== 'function' ) {
|
|
91
|
-
throw `Request method ${method} must be a function. Got: ${handlers[method]}`
|
|
92
|
-
}
|
|
93
|
-
route.handler[method] = handler
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return route
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Load the routes
|
|
101
|
-
*/
|
|
102
|
-
const init = async () => {
|
|
103
|
-
if( !state.initializing ) {
|
|
104
|
-
state.initializing = true
|
|
105
|
-
const fullRouteDir = path.resolve( serverConfig.current.routeDir )
|
|
106
|
-
if( !fs.existsSync( fullRouteDir ) ) {
|
|
107
|
-
throw `can't find route directory: ${fullRouteDir}`
|
|
108
|
-
}
|
|
109
|
-
let appMiddleware = mergeMiddleware( serverConfig.current.middleware || [], {} )
|
|
110
|
-
return Promise.all(
|
|
111
|
-
fs.readdirSync( fullRouteDir, { withFileTypes: true } )
|
|
112
|
-
.map(
|
|
113
|
-
( f ) => findRoutes( f, fullRouteDir + '/' + f.name, appMiddleware )
|
|
114
|
-
)
|
|
115
|
-
).then(
|
|
116
|
-
routes => {
|
|
117
|
-
state.initializing = false
|
|
118
|
-
state.routes = routes.reduce(
|
|
119
|
-
( res, route ) => {
|
|
120
|
-
for( let name of Object.keys( route ) ) {
|
|
121
|
-
if( res[name] ) {
|
|
122
|
-
throw `Duplicate route name ${name} found in path: ${fullRouteDir}`
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
return Object.assign( res, route )
|
|
126
|
-
}, {} )
|
|
127
|
-
return state.routes
|
|
128
|
-
}
|
|
129
|
-
).then( addNodeModuleRoutes )
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
const getRoute = ( part, route, pathParameters ) => {
|
|
134
|
-
if( part in route ) {
|
|
135
|
-
return route[part]
|
|
136
|
-
} else if( route.variable ) {
|
|
137
|
-
pathParameters[route.variable.key] = serverConfig.current.decodePathParameters ? decodeURIComponent( part.replace( /\+/g, '%20' ) ) : part
|
|
138
|
-
return route.variable || {}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
module.exports = {
|
|
143
|
-
init,
|
|
144
|
-
HTTP_METHODS,
|
|
145
|
-
validateMiddleware,
|
|
146
|
-
/**
|
|
147
|
-
* Find a handler for the given url
|
|
148
|
-
* @param url
|
|
149
|
-
* @returns {{}|{handler: ({}|{GET}|*|{GET}), pathParameters}}
|
|
150
|
-
*/
|
|
151
|
-
find: ( url ) => {
|
|
152
|
-
let prefix = serverConfig.current.routePrefix
|
|
153
|
-
let path = url.path
|
|
154
|
-
if( prefix ) {
|
|
155
|
-
if( !url.path.startsWith( '/' + prefix ) ) {
|
|
156
|
-
return {}
|
|
157
|
-
}
|
|
158
|
-
path = url.path.substr( 1 + ( prefix && prefix.length + 1 || 0 ) )
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
let route = state.routes
|
|
162
|
-
let buf = ''
|
|
163
|
-
let pathParameters = {}
|
|
164
|
-
|
|
165
|
-
if( path === '' || path === '/' ) {
|
|
166
|
-
route = getRoute( 'index', route, pathParameters ) || {}
|
|
167
|
-
} else {
|
|
168
|
-
for( let i = 1; i <= path.length; i++ ) {
|
|
169
|
-
if( i === path.length || path[i] === '/' ) {
|
|
170
|
-
//trailing or consecutive /
|
|
171
|
-
if(buf === '') continue
|
|
172
|
-
route = getRoute( buf, route, pathParameters )
|
|
173
|
-
buf = ''
|
|
174
|
-
if( !route ) {
|
|
175
|
-
route = {}
|
|
176
|
-
break
|
|
177
|
-
} else if( route.catchall ) {
|
|
178
|
-
break
|
|
179
|
-
}
|
|
180
|
-
} else {
|
|
181
|
-
buf += path[i]
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return {
|
|
186
|
-
handler: route && ( route.handler || ( route.index && route.index.handler ) ),
|
|
187
|
-
pathParameters: pathParameters,
|
|
188
|
-
middleware: route && ( route.middleware || ( route.index && route.index.middleware ) )
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
package/src/secure.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
const serverConfig = require( './serverConfig' )
|
|
2
|
-
const fs = require( 'fs' )
|
|
3
|
-
const log = require( './log' )
|
|
4
|
-
const path = require( 'path' )
|
|
5
|
-
const uws = require( 'uWebSockets.js' )
|
|
6
|
-
|
|
7
|
-
const startHttpRedirect = () => {
|
|
8
|
-
//redirect http to https
|
|
9
|
-
let port = serverConfig.current.port;
|
|
10
|
-
uws.App().any( "/*",
|
|
11
|
-
( req, res ) => {
|
|
12
|
-
try {
|
|
13
|
-
res.writeHead( 301, { 'Location': `https://${req.headers.host}:${serverConfig.current.port}${req.url}` } )
|
|
14
|
-
res.end()
|
|
15
|
-
} catch( e ) {
|
|
16
|
-
log.error( `Failed to handle http request on port ${serverConfig.current.port}`, req.url, e )
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
).listen( serverConfig.current.host || '0.0.0.0', port, ( token ) => {
|
|
20
|
-
if( token ) {
|
|
21
|
-
log.gne( `Http redirect server initialized at ${new Date().toISOString()} and listening on port ${port}` )
|
|
22
|
-
} else {
|
|
23
|
-
throw new Error( `Failed to start server on port ${port}` )
|
|
24
|
-
}
|
|
25
|
-
} )
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
let startServer = () => {
|
|
29
|
-
const secure = serverConfig.current.secure
|
|
30
|
-
if( !secure.key || !secure.cert ) throw 'You must supply an secure key and cert!'
|
|
31
|
-
let keyPath = path.resolve( secure.key )
|
|
32
|
-
let certPath = path.resolve( secure.cert )
|
|
33
|
-
if( !fs.existsSync( keyPath ) ) throw `Can't find https key file: ${keyPath}`
|
|
34
|
-
if( !fs.existsSync( certPath ) ) throw `Can't find https cert file: ${keyPath}`
|
|
35
|
-
let port = secure.port || 14420;
|
|
36
|
-
uws.App( {
|
|
37
|
-
key_file_name: secure.key,
|
|
38
|
-
cert_file_name: secure.cert
|
|
39
|
-
} ).listen( serverConfig.current.host || '0.0.0.0', port, ( token ) => {
|
|
40
|
-
if( token ) {
|
|
41
|
-
log.gne( `Https server initialized at ${new Date().toISOString()} and listening on port ${port}` )
|
|
42
|
-
} else {
|
|
43
|
-
throw new Error( `Failed to start server on port ${port}` )
|
|
44
|
-
}
|
|
45
|
-
} )
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
module.exports = {
|
|
49
|
-
startHttps: () => {
|
|
50
|
-
startServer()
|
|
51
|
-
startHttpRedirect()
|
|
52
|
-
}
|
|
53
|
-
}
|
package/src/serverConfig.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
const content = require( './content' )
|
|
2
|
-
const { validateMiddleware } = require( './middleware.js' )
|
|
3
|
-
const log = require( './log' )
|
|
4
|
-
let current = {}
|
|
5
|
-
const defaultHeaders = {
|
|
6
|
-
acceptsDefault: '*/*',
|
|
7
|
-
defaultContentType: '*/*'
|
|
8
|
-
}
|
|
9
|
-
//this is mainly for performance reason
|
|
10
|
-
const nonsense = [
|
|
11
|
-
'Is it getting hot in here?',
|
|
12
|
-
'Ouch...',
|
|
13
|
-
'Not feeling so good',
|
|
14
|
-
'Look a squirrel!',
|
|
15
|
-
'I swear I only had 3',
|
|
16
|
-
'I see a light...',
|
|
17
|
-
'Totally zooted',
|
|
18
|
-
'Where are my pants?',
|
|
19
|
-
'Somebody, anyone, help!',
|
|
20
|
-
'What was I doing again?',
|
|
21
|
-
'Burninating the codeessss',
|
|
22
|
-
'Leeeeerrrooooyyy Jeeenkins',
|
|
23
|
-
'At least I have chicken'
|
|
24
|
-
]
|
|
25
|
-
|
|
26
|
-
module.exports = {
|
|
27
|
-
current,
|
|
28
|
-
randomNonsense: () => `~[OHNO]{${nonsense[Math.floor( Math.random() * nonsense.length )]}}~`,
|
|
29
|
-
init( config ) {
|
|
30
|
-
Object.assign( current, config )
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if( !config.hasOwnProperty( 'decodePathParameters' ) ) {
|
|
34
|
-
current.decodePathParameters = true
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if( !config.hasOwnProperty( 'parseCookie' ) ) {
|
|
38
|
-
current.parseCookie = true
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
current.acceptsDefault = config.acceptsDefault || defaultHeaders.acceptsDefault
|
|
42
|
-
current.defaultContentType = config.defaultContentType || defaultHeaders.defaultContentType
|
|
43
|
-
|
|
44
|
-
content.initContentHandlers(config.contentHandlers||{}, current.acceptsDefault)
|
|
45
|
-
current.resolveWithoutExtension = current.resolveWithoutExtension || []
|
|
46
|
-
if( !Array.isArray( current.resolveWithoutExtension ) ) {
|
|
47
|
-
current.resolveWithoutExtension = [current.resolveWithoutExtension]
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if( current.resolveWithoutExtension.indexOf( '.htm' ) === -1 ) {
|
|
51
|
-
current.resolveWithoutExtension.push( '.htm' )
|
|
52
|
-
}
|
|
53
|
-
if( current.resolveWithoutExtension.indexOf( '.html' ) === -1 ) {
|
|
54
|
-
current.resolveWithoutExtension.push( '.html' )
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if( current.middleware ) {
|
|
58
|
-
validateMiddleware( current.middleware )
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if( !current.hasOwnProperty( 'logAccess' ) ) {
|
|
62
|
-
current.logAccess = true
|
|
63
|
-
}
|
|
64
|
-
log.setLogAccess( current.logAccess )
|
|
65
|
-
if( current.hasOwnProperty( 'logLevel' ) ) {
|
|
66
|
-
log.setLogLevel( current.logLevel )
|
|
67
|
-
}
|
|
68
|
-
current.port = config.port || 10420
|
|
69
|
-
}
|
|
70
|
-
}
|
package/src/start.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
const serverConfig = require( './serverConfig' )
|
|
2
|
-
const dispatcher = require( './dispatcher' )
|
|
3
|
-
const routes = require( './routes' )
|
|
4
|
-
const secure = require( './secure' )
|
|
5
|
-
const log = require( './log' )
|
|
6
|
-
const uws = require( 'uWebSockets.js' )
|
|
7
|
-
const { randomNonsense } = require( "./serverConfig" );
|
|
8
|
-
|
|
9
|
-
module.exports = async function( config ) {
|
|
10
|
-
if( !config || !config.routeDir ) {
|
|
11
|
-
throw 'You must supply a config object with at least a routeDir property. routeDir should be a full path.'
|
|
12
|
-
}
|
|
13
|
-
log.gne( 'Starting Spliffy!' )
|
|
14
|
-
serverConfig.init( config )
|
|
15
|
-
log.info( 'Loading routes' )
|
|
16
|
-
await routes.init()
|
|
17
|
-
log.gne( 'Routes Initialized!' )
|
|
18
|
-
|
|
19
|
-
if( config.secure ) {
|
|
20
|
-
secure.startHttps( config.secure )
|
|
21
|
-
} else {
|
|
22
|
-
uws.App().any( '/*', dispatcher )
|
|
23
|
-
.listen( serverConfig.current.host || '0.0.0.0', serverConfig.current.port, ( token ) => {
|
|
24
|
-
if( token ) {
|
|
25
|
-
log.gne( `Server initialized at ${new Date().toISOString()} and listening on port ${serverConfig.current.port}` )
|
|
26
|
-
} else {
|
|
27
|
-
throw new Error( `Failed to start server on port ${serverConfig.current.port}` )
|
|
28
|
-
}
|
|
29
|
-
} )
|
|
30
|
-
}
|
|
31
|
-
process
|
|
32
|
-
.on( 'unhandledRejection', ( reason, p ) => {
|
|
33
|
-
log.error( randomNonsense(), reason, 'Unhandled Rejection at Promise', p )
|
|
34
|
-
} )
|
|
35
|
-
.on( 'uncaughtException', ( err, origin ) => {
|
|
36
|
-
log.error( randomNonsense(), `Caught exception: ${err}\n` +
|
|
37
|
-
`Exception origin: ${origin}` )
|
|
38
|
-
} )
|
|
39
|
-
}
|