@srfnstack/spliffy 0.7.0 → 0.9.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 +16 -7
- package/src/content-types.mjs +75 -0
- package/src/content.mjs +97 -0
- package/src/decorator.mjs +212 -0
- package/src/handler.mjs +249 -0
- package/src/httpStatusCodes.mjs +103 -0
- package/src/index.mjs +31 -0
- package/src/log.mjs +59 -0
- package/src/middleware.mjs +90 -0
- package/src/nodeModuleHandler.mjs +61 -0
- package/src/routes.mjs +182 -0
- package/src/server.mjs +126 -0
- package/src/serverConfig.mjs +96 -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 -98
- package/src/decorator.js +0 -202
- package/src/handler.js +0 -262
- package/src/index.js +0 -29
- package/src/log.js +0 -43
- package/src/middleware.js +0 -116
- package/src/nodeModuleHandler.js +0 -63
- package/src/routes.js +0 -137
- package/src/secure.js +0 -39
- package/src/server.js +0 -83
- package/src/serverConfig.js +0 -69
- package/src/start.js +0 -21
- package/src/staticHandler.js +0 -77
- package/src/url.js +0 -26
package/src/handler.js
DELETED
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
const log = require( './log' )
|
|
2
|
-
const content = require( './content' )
|
|
3
|
-
const { executeMiddleware } = require( "./middleware" );
|
|
4
|
-
const { decorateResponse } = require( './decorator.js' )
|
|
5
|
-
const { decorateRequest } = require( './decorator.js' )
|
|
6
|
-
const uuid = require( 'uuid' ).v4
|
|
7
|
-
const { Readable } = require( 'stream' )
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Execute the handler
|
|
11
|
-
* @param url The url being requested
|
|
12
|
-
* @param res The uws response object
|
|
13
|
-
* @param req The uws request object
|
|
14
|
-
* @param body The request body
|
|
15
|
-
* @param handler The handler function for the route
|
|
16
|
-
* @param middleware The middleware that applies to this request
|
|
17
|
-
* @param errorTransformer An errorTransformer to convert error objects into response data
|
|
18
|
-
*/
|
|
19
|
-
const executeHandler = async ( url, res, req, body, handler, middleware, errorTransformer ) => {
|
|
20
|
-
try {
|
|
21
|
-
if( body )
|
|
22
|
-
body = content.deserialize( body, req.headers['content-type'] )
|
|
23
|
-
} catch( e ) {
|
|
24
|
-
log.error( 'Failed to parse request.', e )
|
|
25
|
-
end( res, 400, 'Failed to parse request body' )
|
|
26
|
-
return
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
let handled = handler(
|
|
31
|
-
{
|
|
32
|
-
url,
|
|
33
|
-
body,
|
|
34
|
-
headers: req.headers,
|
|
35
|
-
req,
|
|
36
|
-
res
|
|
37
|
-
} )
|
|
38
|
-
if( handled && typeof handled.then == 'function' ) {
|
|
39
|
-
await handled.then( ( h ) => finalizeResponse( req, res, h ) )
|
|
40
|
-
.catch(
|
|
41
|
-
e => {
|
|
42
|
-
let refId = uuid()
|
|
43
|
-
log.error( 'handler failed', e, refId )
|
|
44
|
-
endError( res, e, refId, errorTransformer )
|
|
45
|
-
}
|
|
46
|
-
)
|
|
47
|
-
} else {
|
|
48
|
-
finalizeResponse( req, res, handled )
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
} catch( e ) {
|
|
52
|
-
let refId = uuid()
|
|
53
|
-
log.error( 'handler failed', e, refId )
|
|
54
|
-
await tryExecuteMiddleware( middleware, req, res, e, errorTransformer, refId )
|
|
55
|
-
endError( res, e, refId, errorTransformer )
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const endError = ( res, e, refId, errorTransformer ) => {
|
|
60
|
-
if( e.body && typeof e.body !== 'string' ) {
|
|
61
|
-
e.body = JSON.stringify( e.body )
|
|
62
|
-
}
|
|
63
|
-
if( typeof errorTransformer === 'function' ) {
|
|
64
|
-
e = errorTransformer( e, refId )
|
|
65
|
-
}
|
|
66
|
-
end( res, e.statusCode || 500, ( e.statusMessage || 'Internal Server Error' ) + ' refId: ' + refId, e.body || '' )
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const end = ( res, code, message, body ) => {
|
|
70
|
-
//status set directly on res wins
|
|
71
|
-
res.statusCode = res.statusCode || code
|
|
72
|
-
res.statusMessage = res.statusMessage || message
|
|
73
|
-
if( body instanceof Readable || res.streaming ) {
|
|
74
|
-
res.streaming = true
|
|
75
|
-
if( body instanceof Readable ) {
|
|
76
|
-
res.flushHeaders()
|
|
77
|
-
pipeResponse( res, body )
|
|
78
|
-
}
|
|
79
|
-
//handler is responsible for ending the response if they are streaming
|
|
80
|
-
} else {
|
|
81
|
-
res.end( serializeBody( body, res ) || '' )
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const writeAccess = function( req, res ) {
|
|
86
|
-
const start = new Date().getTime()
|
|
87
|
-
return () => {
|
|
88
|
-
log.access( req.remoteAddress, res.proxiedRemoteAddress || '', res.statusCode, req.method, req.url, new Date().getTime() - start + 'ms' )
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const finalizeResponse = ( req, res, handled ) => {
|
|
93
|
-
if( !res.finalized ) {
|
|
94
|
-
res.finalized = true
|
|
95
|
-
if( !handled ) {
|
|
96
|
-
//if no error was thrown, assume everything is fine. Otherwise each handler must return truthy which is un-necessary for methods that don't need to return anything
|
|
97
|
-
end( res, 200, 'OK' )
|
|
98
|
-
} else {
|
|
99
|
-
//if the returned object has known fields, treat it as a response object instead of the body
|
|
100
|
-
if( handled.body || handled.statusMessage || handled.statusCode || handled.headers ) {
|
|
101
|
-
if( handled.headers ) {
|
|
102
|
-
res.assignHeaders( handled.headers )
|
|
103
|
-
}
|
|
104
|
-
end( res, handled.statusCode || 200, handled.statusMessage || 'OK', handled.body )
|
|
105
|
-
} else {
|
|
106
|
-
end( res, 200, 'OK', handled )
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const pipeResponse = ( res, readStream, errorTransformer ) => {
|
|
113
|
-
readStream.on( 'data', res.write )
|
|
114
|
-
.on( 'end', res.end )
|
|
115
|
-
.on( 'error', e => {
|
|
116
|
-
try {
|
|
117
|
-
readStream.destroy()
|
|
118
|
-
} finally {
|
|
119
|
-
endError( res, e, uuid(), errorTransformer )
|
|
120
|
-
}
|
|
121
|
-
} )
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const serializeBody = ( body, res ) => {
|
|
125
|
-
let contentType = res.getHeader( 'content-type' )
|
|
126
|
-
if( typeof body === 'string' ) {
|
|
127
|
-
if( !contentType ) {
|
|
128
|
-
res.headers['content-type'] = 'text/plain'
|
|
129
|
-
}
|
|
130
|
-
return body
|
|
131
|
-
} else if( body instanceof Readable ) {
|
|
132
|
-
return body
|
|
133
|
-
}
|
|
134
|
-
let serialized = content.serialize( body, contentType )
|
|
135
|
-
|
|
136
|
-
if( serialized && serialized.contentType && !contentType ) {
|
|
137
|
-
res.headers['content-type'] = serialized.contentType
|
|
138
|
-
}
|
|
139
|
-
return serialized && serialized.data || ''
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async function tryExecuteMiddleware( middleware, req, res, e, errorTransformer, refId ) {
|
|
143
|
-
if( !middleware ) return
|
|
144
|
-
|
|
145
|
-
let applicableMiddleware = middleware[req.method]
|
|
146
|
-
if( middleware.ALL ) {
|
|
147
|
-
if( applicableMiddleware ) applicableMiddleware = middleware.ALL.concat( applicableMiddleware )
|
|
148
|
-
else applicableMiddleware = middleware.ALL
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
if( !applicableMiddleware || applicableMiddleware.length === 0 ) {
|
|
152
|
-
return
|
|
153
|
-
}
|
|
154
|
-
const errorMiddleware = applicableMiddleware.filter( mw => mw.length === 4 )
|
|
155
|
-
const middlewarez = applicableMiddleware.filter( mw => mw.length === 3 )
|
|
156
|
-
try {
|
|
157
|
-
if( e )
|
|
158
|
-
await executeMiddleware( middlewarez, errorMiddleware, req, res, e )
|
|
159
|
-
else
|
|
160
|
-
await executeMiddleware( middlewarez, errorMiddleware, req, res )
|
|
161
|
-
} catch( e ) {
|
|
162
|
-
if( !refId ) refId = uuid()
|
|
163
|
-
console.log( 'Failed executing middleware while handling error: ' + e )
|
|
164
|
-
endError( res, e, refId, errorTransformer )
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
let currentDate = new Date().toUTCString()
|
|
169
|
-
setInterval( () => currentDate = new Date().toUTCString(), 1000 )
|
|
170
|
-
|
|
171
|
-
const handleRequest = async ( req, res, handler, middleware, errorTransformer ) => {
|
|
172
|
-
res.headers['server'] = 'uWebSockets.js'
|
|
173
|
-
res.headers['date'] = currentDate
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
let reqBody
|
|
177
|
-
if( !handler.streamRequestBody ) {
|
|
178
|
-
let buffer
|
|
179
|
-
reqBody = await new Promise(
|
|
180
|
-
resolve =>
|
|
181
|
-
res.onData( async ( data, isLast ) => {
|
|
182
|
-
if( isLast ) {
|
|
183
|
-
buffer = data.byteLength > 0 ? Buffer.concat( [buffer, Buffer.from( data )].filter( b => b ) ) : buffer
|
|
184
|
-
resolve( buffer )
|
|
185
|
-
}
|
|
186
|
-
buffer = Buffer.concat( [buffer, Buffer.from( data )].filter( b => b ) )
|
|
187
|
-
} )
|
|
188
|
-
)
|
|
189
|
-
} else {
|
|
190
|
-
reqBody = new Readable( {
|
|
191
|
-
read: () => {
|
|
192
|
-
}
|
|
193
|
-
} )
|
|
194
|
-
res.onData( async ( data, isLast ) => {
|
|
195
|
-
if( data.byteLength === 0 && !isLast ) return
|
|
196
|
-
//data must be copied so it isn't lost
|
|
197
|
-
reqBody.push( Buffer.concat( [Buffer.from( data )] ) )
|
|
198
|
-
if( isLast ) {
|
|
199
|
-
reqBody.push( null )
|
|
200
|
-
}
|
|
201
|
-
} )
|
|
202
|
-
}
|
|
203
|
-
await tryExecuteMiddleware( middleware, req, res, errorTransformer )
|
|
204
|
-
if( !res.writableEnded && !res.ended ) {
|
|
205
|
-
await executeHandler( req.spliffyUrl, res, req, reqBody, handler, middleware, errorTransformer )
|
|
206
|
-
}
|
|
207
|
-
} catch( e ) {
|
|
208
|
-
let refId = uuid()
|
|
209
|
-
log.error( 'Handling request failed', e, refId )
|
|
210
|
-
await tryExecuteMiddleware( middleware, req, res, e, errorTransformer, refId );
|
|
211
|
-
if( !res.writableEnded )
|
|
212
|
-
endError( res, e, refId, errorTransformer )
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD', 'CONNECT', 'TRACE']
|
|
216
|
-
module.exports =
|
|
217
|
-
{
|
|
218
|
-
create: ( handler, middleware, pathParameters, config ) => function( res, req ) {
|
|
219
|
-
try {
|
|
220
|
-
req = decorateRequest( req, pathParameters, res, config );
|
|
221
|
-
res = decorateResponse( res, req, finalizeResponse, config.errorTransformer, endError );
|
|
222
|
-
|
|
223
|
-
if( config.logAccess ) {
|
|
224
|
-
res.onEnd = writeAccess( req, res )
|
|
225
|
-
}
|
|
226
|
-
handleRequest( req, res, handler, middleware, config.errorTransformer )
|
|
227
|
-
} catch( e ) {
|
|
228
|
-
log.error( 'Failed handling request', e )
|
|
229
|
-
}
|
|
230
|
-
},
|
|
231
|
-
notFound: config => ( res, req ) => {
|
|
232
|
-
try {
|
|
233
|
-
let params = config.defaultRoute && config.defaultRoute.pathParameters || []
|
|
234
|
-
req = decorateRequest( req, params, res, config );
|
|
235
|
-
res = decorateResponse( res, req, finalizeResponse );
|
|
236
|
-
if( config.logAccess ) {
|
|
237
|
-
res.onEnd = writeAccess( req, res )
|
|
238
|
-
}
|
|
239
|
-
if( config.defaultRoute && typeof config.defaultRoute ) {
|
|
240
|
-
let route = config.defaultRoute
|
|
241
|
-
if( route.handlers && route.handlers[req.method] ) {
|
|
242
|
-
handleRequest( req, res,
|
|
243
|
-
config.defaultRoute.handlers[req.method],
|
|
244
|
-
config.defaultRoute.middleware,
|
|
245
|
-
config.errorTransformer
|
|
246
|
-
)
|
|
247
|
-
} else {
|
|
248
|
-
res.statusCode = 405
|
|
249
|
-
res.statusMessage = 'Method Not Allowed'
|
|
250
|
-
res.end()
|
|
251
|
-
}
|
|
252
|
-
} else {
|
|
253
|
-
res.statusCode = 404
|
|
254
|
-
res.statusMessage = 'Not Found'
|
|
255
|
-
res.end()
|
|
256
|
-
}
|
|
257
|
-
} catch( e ) {
|
|
258
|
-
log.error( 'Failed handling request', e )
|
|
259
|
-
}
|
|
260
|
-
},
|
|
261
|
-
HTTP_METHODS
|
|
262
|
-
}
|
package/src/index.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
const log = require( './log' )
|
|
2
|
-
let url = require('./url')
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Startup function for the spliffy server
|
|
6
|
-
* Startup will exponentially back off on consecutive failures
|
|
7
|
-
* @param config See https://github.com/narcolepticsnowman/spliffy#config
|
|
8
|
-
* @returns {Promise<Server>} Either the https server if https is configured or the http server
|
|
9
|
-
*/
|
|
10
|
-
const spliffy = ( config ) => require( './start' )( config )
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* A helper for creating a redirect handler
|
|
14
|
-
* @param location The location to redirect to
|
|
15
|
-
* @param permanent Whether this is a permanent redirect or not
|
|
16
|
-
*/
|
|
17
|
-
spliffy.redirect = ( location, permanent = true ) => () => ( {
|
|
18
|
-
statusCode: permanent ? 301 : 302,
|
|
19
|
-
statusMessage: permanent ? 'Moved Permanently' : 'Found',
|
|
20
|
-
headers: {
|
|
21
|
-
'location': location
|
|
22
|
-
}
|
|
23
|
-
} )
|
|
24
|
-
|
|
25
|
-
spliffy.log = log
|
|
26
|
-
spliffy.parseQuery = url.parseQuery
|
|
27
|
-
spliffy.setMultiValueKey = url.setMultiValueKey
|
|
28
|
-
|
|
29
|
-
module.exports = spliffy
|
package/src/log.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
const inspect = require( 'util' ).inspect
|
|
2
|
-
const levelOrder = { DEBUG: 0, INFO: 1, 'GOOD NEWS EVERYONE!': 1, WARN: 2, ERROR: 3, NONE: 4 }
|
|
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,116 +0,0 @@
|
|
|
1
|
-
const log = require( './log' )
|
|
2
|
-
/**
|
|
3
|
-
* middleware is stored as an object where the properties are request methods the middleware applies to
|
|
4
|
-
* if a middleware applies to all methods, the property ALL is used
|
|
5
|
-
* example:
|
|
6
|
-
* {
|
|
7
|
-
* GET: [(req,res,next)=>console.log('ice cream man')]
|
|
8
|
-
* POST: [(req,res,next)=>console.log('gelato')]
|
|
9
|
-
* ALL: [(req,res,next)=>console.log('bruce banner')]
|
|
10
|
-
* }
|
|
11
|
-
*/
|
|
12
|
-
const mergeMiddleware = ( incoming, existing ) => {
|
|
13
|
-
const mergeInto = cloneMiddleware( existing )
|
|
14
|
-
|
|
15
|
-
validateMiddleware( incoming )
|
|
16
|
-
if( Array.isArray( incoming ) ) {
|
|
17
|
-
mergeInto.ALL = ( existing.ALL || [] ).concat( incoming )
|
|
18
|
-
} else if( typeof incoming === 'object' ) {
|
|
19
|
-
for( let method in incoming ) {
|
|
20
|
-
let upMethod = method.toUpperCase()
|
|
21
|
-
mergeInto[upMethod] = ( mergeInto[method] || [] ).concat( incoming[upMethod] || [] )
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return mergeInto
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const cloneMiddleware = ( middleware ) => {
|
|
28
|
-
const clone = { ...middleware }
|
|
29
|
-
for( let method in middleware ) {
|
|
30
|
-
clone[method] = [...( middleware[method] || [] )]
|
|
31
|
-
}
|
|
32
|
-
return clone
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Ensure the given middleware is valid
|
|
37
|
-
* @param middleware
|
|
38
|
-
*/
|
|
39
|
-
const validateMiddleware = ( middleware ) => {
|
|
40
|
-
if( Array.isArray( middleware ) ) {
|
|
41
|
-
validateMiddlewareArray( middleware )
|
|
42
|
-
} else if( typeof middleware === 'object' ) {
|
|
43
|
-
for( let method in middleware ) {
|
|
44
|
-
//ensure methods are always available as uppercase
|
|
45
|
-
let upMethod = method.toUpperCase()
|
|
46
|
-
middleware[upMethod] = middleware[method]
|
|
47
|
-
validateMiddlewareArray( middleware[upMethod] )
|
|
48
|
-
}
|
|
49
|
-
} else {
|
|
50
|
-
throw new Error( 'Invalid middleware definition: ' + middleware )
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const validateMiddlewareArray = ( arr ) => {
|
|
55
|
-
if( !Array.isArray( arr ) ) {
|
|
56
|
-
throw 'middleware must be an array of functions'
|
|
57
|
-
}
|
|
58
|
-
for(let f of arr){
|
|
59
|
-
if( typeof f !== 'function' ) {
|
|
60
|
-
throw 'Each element in the array of middleware must be a function'
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function executeMiddleware( middleware, errorMiddleware, req, res, reqErr ) {
|
|
66
|
-
let err
|
|
67
|
-
await new Promise( ( resolve, reject ) => {
|
|
68
|
-
let current = -1
|
|
69
|
-
let isError = false
|
|
70
|
-
const next = ( mwErr ) => {
|
|
71
|
-
if( !isError && mwErr ) {
|
|
72
|
-
isError = true
|
|
73
|
-
current = -1
|
|
74
|
-
}
|
|
75
|
-
if( res.writableEnded ) {
|
|
76
|
-
resolve()
|
|
77
|
-
return
|
|
78
|
-
}
|
|
79
|
-
current++
|
|
80
|
-
if( ( isError && current === errorMiddleware.length ) ||
|
|
81
|
-
( !isError && current === middleware.length )
|
|
82
|
-
) {
|
|
83
|
-
if( mwErr )
|
|
84
|
-
reject( mwErr )
|
|
85
|
-
resolve()
|
|
86
|
-
} else {
|
|
87
|
-
try {
|
|
88
|
-
let mw = isError ? errorMiddleware[current] : middleware[current]
|
|
89
|
-
if( mwErr ) {
|
|
90
|
-
mw( mwErr, req, res, next )
|
|
91
|
-
} else {
|
|
92
|
-
mw( req, res, next )
|
|
93
|
-
}
|
|
94
|
-
} catch( e ) {
|
|
95
|
-
|
|
96
|
-
log.error( 'Middleware threw exception', e )
|
|
97
|
-
next( e )
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
next( reqErr )
|
|
103
|
-
} ).catch( e => {
|
|
104
|
-
err = e
|
|
105
|
-
return null
|
|
106
|
-
} )
|
|
107
|
-
if(err) throw err
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
module.exports = {
|
|
111
|
-
validateMiddlewareArray,
|
|
112
|
-
cloneMiddleware,
|
|
113
|
-
validateMiddleware,
|
|
114
|
-
mergeMiddleware,
|
|
115
|
-
executeMiddleware
|
|
116
|
-
}
|
package/src/nodeModuleHandler.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
const fs = require( 'fs' )
|
|
2
|
-
const path = require( 'path' )
|
|
3
|
-
const { mergeMiddleware } = require( "./middleware" );
|
|
4
|
-
const staticHandler = require( './staticHandler' )
|
|
5
|
-
const { getContentTypeByExtension } = require( './content' )
|
|
6
|
-
|
|
7
|
-
const stripLeadingSlash = p => p.startsWith( '/' ) ? p.substr( 1 ) : p
|
|
8
|
-
|
|
9
|
-
module.exports = {
|
|
10
|
-
/**
|
|
11
|
-
This method will add all of the configured node_module files to the given routes.
|
|
12
|
-
The configured node moduleRoutes must be explicit files, no pattern matching is supported.
|
|
13
|
-
Generating the list of files using pattern matching yourself is highly discouraged.
|
|
14
|
-
It is much safer to explicitly list every file you wish to be served so you don't inadvertently serve additional files.
|
|
15
|
-
*/
|
|
16
|
-
getNodeModuleRoutes(config) {
|
|
17
|
-
let nodeModuleRoutes = config.nodeModuleRoutes;
|
|
18
|
-
let routes = []
|
|
19
|
-
if( nodeModuleRoutes && typeof nodeModuleRoutes === 'object' ) {
|
|
20
|
-
const nodeModulesDir = nodeModuleRoutes.nodeModulesPath ? path.resolve( nodeModuleRoutes.nodeModulesPath ) : path.resolve( config.routeDir, '..', 'node_modules' )
|
|
21
|
-
if( !fs.existsSync( nodeModulesDir ) ) {
|
|
22
|
-
throw new Error( `Unable to find node_modules dir at ${nodeModulesDir}` )
|
|
23
|
-
}
|
|
24
|
-
let prefix = stripLeadingSlash(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 mw = {}
|
|
44
|
-
mergeMiddleware( config.middleware, mw )
|
|
45
|
-
mergeMiddleware( nodeModuleRoutes.middleware || {}, mw )
|
|
46
|
-
routes.push( {
|
|
47
|
-
pathParameters: [],
|
|
48
|
-
urlPath,
|
|
49
|
-
filePath,
|
|
50
|
-
handlers: staticHandler.create(
|
|
51
|
-
filePath, getContentTypeByExtension(lastPart, config.staticContentTypes),
|
|
52
|
-
config.cacheStatic, config.staticCacheControl
|
|
53
|
-
),
|
|
54
|
-
middleware: mw
|
|
55
|
-
} )
|
|
56
|
-
} else {
|
|
57
|
-
console.warn( `The specified node_modules file: ${file} does not exist and will not be served.` )
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return routes
|
|
62
|
-
}
|
|
63
|
-
}
|
package/src/routes.js
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
const { validateMiddleware, mergeMiddleware } = require( "./middleware" )
|
|
2
|
-
const staticHandler = require( './staticHandler' )
|
|
3
|
-
const { getContentTypeByExtension } = require( "./content" )
|
|
4
|
-
const fs = require( 'fs' )
|
|
5
|
-
const path = require( 'path' )
|
|
6
|
-
const { HTTP_METHODS } = require( './handler' )
|
|
7
|
-
|
|
8
|
-
const isVariable = part => part.startsWith( '$' )
|
|
9
|
-
const getVariableName = part => part.substr( 1 )
|
|
10
|
-
const getPathPart = name => {
|
|
11
|
-
if( name === 'index' ) {
|
|
12
|
-
return ''
|
|
13
|
-
}
|
|
14
|
-
if( name.startsWith( '$' ) ) {
|
|
15
|
-
return `:${name.substr( 1 )}`
|
|
16
|
-
} else if( name.endsWith( '+' ) ) {
|
|
17
|
-
return `${name.substr( 0, name.length - 1 )}/*`
|
|
18
|
-
} else {
|
|
19
|
-
return name
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
const ignoreHandlerFields = { middleware: true, streamRequestBody: true }
|
|
23
|
-
const doFindRoutes = ( config, currentFile, filePath, urlPath, pathParameters, inheritedMiddleware ) => {
|
|
24
|
-
let routes = []
|
|
25
|
-
let name = currentFile.name;
|
|
26
|
-
if( currentFile.isDirectory() ) {
|
|
27
|
-
if( isVariable( name ) ) {
|
|
28
|
-
pathParameters = pathParameters.concat( getVariableName( name ) )
|
|
29
|
-
}
|
|
30
|
-
const files = fs.readdirSync( filePath, { withFileTypes: true } )
|
|
31
|
-
|
|
32
|
-
const dirMiddleware = files
|
|
33
|
-
.filter( f => f.name.endsWith( '.mw.js' ) )
|
|
34
|
-
.map( f => {
|
|
35
|
-
let mw = require( filePath + '/' + f.name )
|
|
36
|
-
if( !mw.middleware ) {
|
|
37
|
-
throw new Error( `${filePath + '/' + f.name} must export a middleware property` )
|
|
38
|
-
}
|
|
39
|
-
validateMiddleware( mw.middleware )
|
|
40
|
-
return mw.middleware
|
|
41
|
-
} )
|
|
42
|
-
.reduce( ( result, incoming ) => mergeMiddleware( incoming, result ), inheritedMiddleware )
|
|
43
|
-
|
|
44
|
-
routes = routes.concat( (
|
|
45
|
-
files
|
|
46
|
-
.filter( f => !f.name.endsWith( '.mw.js' ) )
|
|
47
|
-
.map(
|
|
48
|
-
( f ) => doFindRoutes(
|
|
49
|
-
config,
|
|
50
|
-
f,
|
|
51
|
-
filePath + '/' + f.name,
|
|
52
|
-
urlPath + '/' + getPathPart( name ),
|
|
53
|
-
pathParameters,
|
|
54
|
-
dirMiddleware
|
|
55
|
-
)
|
|
56
|
-
)
|
|
57
|
-
).flat()
|
|
58
|
-
)
|
|
59
|
-
} else if( !config.staticMode && name.endsWith( '.rt.js' ) ) {
|
|
60
|
-
name = name.substr( 0, name.length - '.rt.js'.length )
|
|
61
|
-
if( isVariable( name ) ) {
|
|
62
|
-
pathParameters = pathParameters.concat( getVariableName( name ) )
|
|
63
|
-
}
|
|
64
|
-
let route = {
|
|
65
|
-
pathParameters,
|
|
66
|
-
urlPath: `${urlPath}/${getPathPart( name )}`,
|
|
67
|
-
filePath,
|
|
68
|
-
handlers: {}
|
|
69
|
-
}
|
|
70
|
-
const handlers = require( filePath )
|
|
71
|
-
route.middleware = mergeMiddleware( handlers.middleware || [], inheritedMiddleware )
|
|
72
|
-
for( let method of Object.keys( handlers ).filter( k => !ignoreHandlerFields[k] ) ) {
|
|
73
|
-
if( HTTP_METHODS.indexOf( method ) === -1 ) {
|
|
74
|
-
throw `Method: ${method} in file ${filePath} is not a valid http method. It must be one of: ${HTTP_METHODS}. Method names must be all uppercase.`
|
|
75
|
-
}
|
|
76
|
-
let loadedHandler = handlers[method]
|
|
77
|
-
let handler = loadedHandler
|
|
78
|
-
if( typeof loadedHandler.handler === 'function' ) {
|
|
79
|
-
handler = loadedHandler.handler
|
|
80
|
-
}
|
|
81
|
-
if( typeof handler !== 'function' ) {
|
|
82
|
-
throw `Request method ${method} in file ${filePath} must be a function. Got: ${handlers[method]}`
|
|
83
|
-
}
|
|
84
|
-
if( !loadedHandler.hasOwnProperty( 'streamRequestBody' ) ) {
|
|
85
|
-
handler.streamRequestBody = handlers.streamRequestBody
|
|
86
|
-
} else {
|
|
87
|
-
handler.streamRequestBody = loadedHandler.streamRequestBody
|
|
88
|
-
}
|
|
89
|
-
route.handlers[method] = handler
|
|
90
|
-
}
|
|
91
|
-
routes.push( route )
|
|
92
|
-
} else {
|
|
93
|
-
if( isVariable( name ) ) {
|
|
94
|
-
pathParameters = pathParameters.concat( getVariableName( name ) )
|
|
95
|
-
}
|
|
96
|
-
let contentType = getContentTypeByExtension( name, config.staticContentTypes )
|
|
97
|
-
let route = {
|
|
98
|
-
pathParameters,
|
|
99
|
-
urlPath: `${urlPath}/${getPathPart( name )}`,
|
|
100
|
-
filePath,
|
|
101
|
-
handlers: staticHandler.create( filePath, contentType,
|
|
102
|
-
config.cacheStatic, config.staticCacheControl ),
|
|
103
|
-
middleware: inheritedMiddleware
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if( !route.handlers.OPTIONS ) {
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
routes.push( route )
|
|
111
|
-
|
|
112
|
-
for( let ext of config.resolveWithoutExtension ) {
|
|
113
|
-
if( name.endsWith( ext ) ) {
|
|
114
|
-
let noExtRoute = Object.assign( {}, route )
|
|
115
|
-
noExtRoute.urlPath = `${urlPath}/${getPathPart( name.substr( 0, name.length - ext.length ) )}`
|
|
116
|
-
routes.push( noExtRoute )
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return routes
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
module.exports = {
|
|
125
|
-
findRoutes(config) {
|
|
126
|
-
const fullRouteDir = path.resolve( config.routeDir )
|
|
127
|
-
if( !fs.existsSync( fullRouteDir ) ) {
|
|
128
|
-
throw `can't find route directory: ${fullRouteDir}`
|
|
129
|
-
}
|
|
130
|
-
let appMiddleware = mergeMiddleware( config.middleware || [], {} )
|
|
131
|
-
return fs.readdirSync( fullRouteDir, { withFileTypes: true } )
|
|
132
|
-
.map(
|
|
133
|
-
f => doFindRoutes( config, f, fullRouteDir + '/' + f.name, '', [], appMiddleware )
|
|
134
|
-
)
|
|
135
|
-
.flat()
|
|
136
|
-
}
|
|
137
|
-
}
|
package/src/secure.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
const fs = require( 'fs' )
|
|
2
|
-
const log = require( './log' )
|
|
3
|
-
const path = require( 'path' )
|
|
4
|
-
const uws = require( 'uWebSockets.js' )
|
|
5
|
-
|
|
6
|
-
const startHttpRedirect = ( host, port) => {
|
|
7
|
-
//redirect http to https
|
|
8
|
-
uws.App().any( "/*",
|
|
9
|
-
( req, res ) => {
|
|
10
|
-
try {
|
|
11
|
-
res.writeHead( 301, { 'Location': `https://${req.headers.host}:${port}${req.url}` } )
|
|
12
|
-
res.end()
|
|
13
|
-
} catch( e ) {
|
|
14
|
-
log.error( `Failed to handle http request on port ${port}`, req.url, e )
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
).listen( host || '0.0.0.0', port, ( token ) => {
|
|
18
|
-
if( token ) {
|
|
19
|
-
log.gne( `Http redirect server initialized at ${new Date().toISOString()} and listening on port ${port}` )
|
|
20
|
-
} else {
|
|
21
|
-
throw new Error( `Failed to start server on port ${port}` )
|
|
22
|
-
}
|
|
23
|
-
} )
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
let getHttpsApp = ({ key, cert }) => {
|
|
27
|
-
if( !key || !cert ) throw 'You must set secure.key and secure.cert in the config to use https!'
|
|
28
|
-
let keyPath = path.resolve( key )
|
|
29
|
-
let certPath = path.resolve( cert )
|
|
30
|
-
if( !fs.existsSync( keyPath ) ) throw `Can't find https key file: ${keyPath}`
|
|
31
|
-
if( !fs.existsSync( certPath ) ) throw `Can't find https cert file: ${keyPath}`
|
|
32
|
-
return uws.App( {
|
|
33
|
-
key_file_name: keyPath,
|
|
34
|
-
cert_file_name: certPath
|
|
35
|
-
} )
|
|
36
|
-
}
|
|
37
|
-
module.exports = {
|
|
38
|
-
getHttpsApp, startHttpRedirect
|
|
39
|
-
}
|