@srfnstack/spliffy 0.7.0 → 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.mjs +21 -0
- 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 -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
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { initContentHandlers } from './content.mjs'
|
|
2
|
+
import { validateMiddleware } from './middleware.mjs'
|
|
3
|
+
import log from './log.mjs'
|
|
4
|
+
|
|
5
|
+
const defaultHeaders = {
|
|
6
|
+
acceptsDefault: '*/*',
|
|
7
|
+
defaultContentType: '*/*'
|
|
8
|
+
}
|
|
9
|
+
// this is mainly for performance reasons
|
|
10
|
+
const nonsense = [
|
|
11
|
+
'I\'m toasted',
|
|
12
|
+
'that hurt',
|
|
13
|
+
'your interwebs!',
|
|
14
|
+
'I see a light...',
|
|
15
|
+
'totally zooted',
|
|
16
|
+
'misplaced my bits',
|
|
17
|
+
'maybe reboot?',
|
|
18
|
+
'what was I doing again?',
|
|
19
|
+
'my cabbages!!!',
|
|
20
|
+
'Leeerrroooyyy Jeeenkins',
|
|
21
|
+
'at least I have chicken'
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
export const randomNonsense = () => `[OH NO, ${nonsense[Math.floor(Math.random() * nonsense.length)]}]`
|
|
25
|
+
|
|
26
|
+
export async function initConfig (userConfig) {
|
|
27
|
+
const config = Object.assign({}, userConfig)
|
|
28
|
+
|
|
29
|
+
if (!('decodePathParameters' in config)) {
|
|
30
|
+
config.decodePathParameters = true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!('parseCookie' in config)) {
|
|
34
|
+
config.parseCookie = true
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
config.acceptsDefault = config.acceptsDefault || defaultHeaders.acceptsDefault
|
|
38
|
+
config.defaultContentType = config.defaultContentType || defaultHeaders.defaultContentType
|
|
39
|
+
|
|
40
|
+
config.contentHandlers = initContentHandlers(config.contentHandlers || {})
|
|
41
|
+
config.resolveWithoutExtension = config.resolveWithoutExtension || []
|
|
42
|
+
if (!Array.isArray(config.resolveWithoutExtension)) {
|
|
43
|
+
config.resolveWithoutExtension = [config.resolveWithoutExtension]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (config.resolveWithoutExtension.indexOf('.htm') === -1) {
|
|
47
|
+
config.resolveWithoutExtension.push('.htm')
|
|
48
|
+
}
|
|
49
|
+
if (config.resolveWithoutExtension.indexOf('.html') === -1) {
|
|
50
|
+
config.resolveWithoutExtension.push('.html')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (config.middleware) {
|
|
54
|
+
validateMiddleware(config.middleware)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!('logAccess' in config)) {
|
|
58
|
+
config.logAccess = true
|
|
59
|
+
}
|
|
60
|
+
if ('logLevel' in config) {
|
|
61
|
+
log.setLogLevel(config.logLevel)
|
|
62
|
+
}
|
|
63
|
+
if (!('ignoreFilesMatching' in config)) {
|
|
64
|
+
config.ignoreFilesMatching = []
|
|
65
|
+
} else if (!Array.isArray(config.ignoreFilesMatching)) {
|
|
66
|
+
config.ignoreFilesMatching = [config.ignoreFilesMatching]
|
|
67
|
+
}
|
|
68
|
+
if (!('allowTestFileRoutes' in config)) {
|
|
69
|
+
config.allowTestFileRoutes = false
|
|
70
|
+
}
|
|
71
|
+
config.port = config.port || 10420
|
|
72
|
+
if (!config.httpPort) {
|
|
73
|
+
config.httpPort = config.port - 1
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (config.logger) {
|
|
77
|
+
log.setLogger(config.logger)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if ((config.httpsKeyFile && !config.httpsCertFile) || (!config.httpsKeyFile && config.httpsCertFile)) {
|
|
81
|
+
throw new Error('You must provide both httpsKeyFile and httpsCertFile')
|
|
82
|
+
}
|
|
83
|
+
return config
|
|
84
|
+
}
|
package/src/start.mjs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { initConfig, randomNonsense } from './serverConfig.mjs'
|
|
2
|
+
import log from './log.mjs'
|
|
3
|
+
import { startServer } from './server.mjs'
|
|
4
|
+
|
|
5
|
+
export default async function (config) {
|
|
6
|
+
if (!config || !config.routeDir) {
|
|
7
|
+
throw new Error('You must supply a config object with at least a routeDir property. routeDir should be a full path.')
|
|
8
|
+
}
|
|
9
|
+
process
|
|
10
|
+
.on('unhandledRejection', (reason, p) => {
|
|
11
|
+
log.error(randomNonsense(), reason, 'Unhandled Rejection at Promise', p)
|
|
12
|
+
})
|
|
13
|
+
.on('uncaughtException', (err, origin) => {
|
|
14
|
+
log.error(randomNonsense(), `Caught unhandled exception: ${err}\n` +
|
|
15
|
+
`Exception origin: ${origin}`)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
log.gne('Starting Spliffy!')
|
|
19
|
+
const configWithDefaults = await initConfig(config)
|
|
20
|
+
return startServer(configWithDefaults).catch(e => {
|
|
21
|
+
log.error(randomNonsense(), 'Exception during startup:', e)
|
|
22
|
+
// Spliffy threw an exception, or a route handler failed to load.
|
|
23
|
+
process.exit(420)
|
|
24
|
+
})
|
|
25
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import etag from 'etag'
|
|
3
|
+
|
|
4
|
+
const readFile = async (fullPath) => await new Promise(
|
|
5
|
+
(resolve, reject) =>
|
|
6
|
+
fs.readFile(fullPath, (err, data) => {
|
|
7
|
+
if (err) reject(err)
|
|
8
|
+
else resolve(data)
|
|
9
|
+
}
|
|
10
|
+
)
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
const writeHeaders = (req, res, tag, stat, contentType, staticCacheControl) => {
|
|
14
|
+
if (req.headers['if-none-match'] === tag) {
|
|
15
|
+
res.statusCode = 304
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
res.writeHead(200, {
|
|
19
|
+
'Content-Type': contentType,
|
|
20
|
+
// content-length should not be included because transfer-encoding is chunked
|
|
21
|
+
// see https://datatracker.ietf.org/doc/html/rfc2616#section-4.4 sub section 3.
|
|
22
|
+
// Not all clients are compliant (node-fetch) and throw instead of ignoring the header as specified
|
|
23
|
+
'Cache-Control': staticCacheControl || 'max-age=600',
|
|
24
|
+
ETag: tag
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const readStat = async path => new Promise((resolve, reject) =>
|
|
29
|
+
fs.stat(path, (err, stats) =>
|
|
30
|
+
err ? reject(err) : resolve(stats)
|
|
31
|
+
))
|
|
32
|
+
|
|
33
|
+
export function createStaticHandler (fullPath, contentType, cacheStatic, staticCacheControl) {
|
|
34
|
+
const cache = {}
|
|
35
|
+
return {
|
|
36
|
+
GET: async ({ req, res }) => {
|
|
37
|
+
if (cacheStatic) {
|
|
38
|
+
if (!cache.exists || !cache.stat) {
|
|
39
|
+
cache.exists = fs.existsSync(fullPath)
|
|
40
|
+
if (cache.exists) {
|
|
41
|
+
cache.stat = await readStat(fullPath)
|
|
42
|
+
cache.content = await readFile(fullPath)
|
|
43
|
+
cache.etag = etag(cache.content)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (!cache.exists) {
|
|
47
|
+
return {
|
|
48
|
+
statusCode: 404
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
writeHeaders(req, res, cache.etag, cache.stat, contentType, staticCacheControl)
|
|
52
|
+
if (res.statusCode === 304) {
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
return cache.content
|
|
56
|
+
} else {
|
|
57
|
+
if (!fs.existsSync(fullPath)) {
|
|
58
|
+
return {
|
|
59
|
+
statusCode: 404
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const stat = await readStat(fullPath)
|
|
63
|
+
writeHeaders(req, res, etag(stat), stat, contentType, staticCacheControl)
|
|
64
|
+
if (res.statusCode === 304) {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
if (stat.size === 0) {
|
|
68
|
+
return ''
|
|
69
|
+
}
|
|
70
|
+
return fs.createReadStream(fullPath)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/url.mjs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function parseQuery (query, decodeQueryParams) {
|
|
2
|
+
const parsed = {}
|
|
3
|
+
if (query) {
|
|
4
|
+
if (decodeQueryParams) {
|
|
5
|
+
query = decodeURIComponent(query.replace(/\+/g, '%20'))
|
|
6
|
+
}
|
|
7
|
+
for (const param of query.split('&')) {
|
|
8
|
+
const eq = param.indexOf('=')
|
|
9
|
+
setMultiValueKey(parsed, param.substr(0, eq), param.substr(eq + 1))
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return parsed
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function setMultiValueKey (obj, key, value) {
|
|
16
|
+
if (key in obj) {
|
|
17
|
+
if (!Array.isArray(obj[key])) {
|
|
18
|
+
obj[key] = [obj[key]]
|
|
19
|
+
}
|
|
20
|
+
obj[key].push(value)
|
|
21
|
+
} else {
|
|
22
|
+
obj[key] = value
|
|
23
|
+
}
|
|
24
|
+
}
|
package/src/content-types.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
'.aac': 'audio/aac',
|
|
3
|
-
'.abw': 'application/x-abiword',
|
|
4
|
-
'.arc': 'application/x-freearc',
|
|
5
|
-
'.avi': 'video/x-msvideo',
|
|
6
|
-
'.azw': 'application/vnd.amazon.ebook',
|
|
7
|
-
'.bin': 'application/octet-stream',
|
|
8
|
-
'.bmp': 'image/bmp',
|
|
9
|
-
'.bz': 'application/x-bzip',
|
|
10
|
-
'.bz2': 'application/x-bzip2',
|
|
11
|
-
'.csh': 'application/x-csh',
|
|
12
|
-
'.css': 'text/css',
|
|
13
|
-
'.csv': 'text/csv',
|
|
14
|
-
'.doc': 'application/msword',
|
|
15
|
-
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
16
|
-
'.eot': 'application/vnd.ms-fontobject',
|
|
17
|
-
'.epub': 'application/epub+zip',
|
|
18
|
-
'.gif': 'image/gif',
|
|
19
|
-
'.htm': 'text/html',
|
|
20
|
-
'.html': 'text/html',
|
|
21
|
-
'.ico': 'image/vnd.microsoft.icon',
|
|
22
|
-
'.ics': 'text/calendar',
|
|
23
|
-
'.jar': 'application/java-archive',
|
|
24
|
-
'.jpeg': 'image/jpeg',
|
|
25
|
-
'.jpg': 'image/jpeg',
|
|
26
|
-
'.js': 'text/javascript',
|
|
27
|
-
'.json': 'application/json',
|
|
28
|
-
'.jsonld': 'application/ld+json',
|
|
29
|
-
'.mid': 'audio/midi',
|
|
30
|
-
'.midi': 'audio/x-midi',
|
|
31
|
-
'.mjs': 'text/javascript',
|
|
32
|
-
'.mp3': 'audio/mpeg',
|
|
33
|
-
'.mpeg': 'video/mpeg',
|
|
34
|
-
'.mpkg': 'application/vnd.apple.installer+xml',
|
|
35
|
-
'.odp': 'application/vnd.oasis.opendocument.presentation',
|
|
36
|
-
'.ods': 'application/vnd.oasis.opendocument.spreadsheet',
|
|
37
|
-
'.odt': 'application/vnd.oasis.opendocument.text',
|
|
38
|
-
'.oga': 'audio/ogg',
|
|
39
|
-
'.ogv': 'video/ogg',
|
|
40
|
-
'.ogg': 'application/ogg',
|
|
41
|
-
'.ogx': 'application/ogg',
|
|
42
|
-
'.otf': 'font/otf',
|
|
43
|
-
'.png': 'image/png',
|
|
44
|
-
'.pdf': 'application/pdf',
|
|
45
|
-
'.ppt': 'application/vnd.ms-powerpoint',
|
|
46
|
-
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
47
|
-
'.rar': 'application/x-rar-compressed',
|
|
48
|
-
'.rtf': 'application/rtf',
|
|
49
|
-
'.sh': 'application/x-sh',
|
|
50
|
-
'.svg': 'image/svg+xml',
|
|
51
|
-
'.swf': 'application/x-shockwave-flash',
|
|
52
|
-
'.tar': 'application/x-tar',
|
|
53
|
-
'.tif': 'image/tiff',
|
|
54
|
-
'.tiff': 'font/ttf',
|
|
55
|
-
'.ts': 'video/mp2t',
|
|
56
|
-
'.ttf': 'font/ttf ',
|
|
57
|
-
'.txt': 'text/plain',
|
|
58
|
-
'.vsd': 'application/vnd.visio',
|
|
59
|
-
'.wav': 'audio/wav',
|
|
60
|
-
'.weba': 'audio/webm',
|
|
61
|
-
'.webm': 'video/webm',
|
|
62
|
-
'.webp': 'image/webp',
|
|
63
|
-
'.woff': 'font/woff',
|
|
64
|
-
'.woff2': 'font/woff2',
|
|
65
|
-
'.xhtml': 'application/xhtml+xml',
|
|
66
|
-
'.xls': 'application/vnd.ms-excel',
|
|
67
|
-
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
68
|
-
'.xml': 'application/xml',
|
|
69
|
-
'.xul': 'application/vnd.mozilla.xul+xml',
|
|
70
|
-
'.zip': 'application/zip',
|
|
71
|
-
'.3gp': 'video/3gpp',
|
|
72
|
-
'.3g2': 'video/3gpp2',
|
|
73
|
-
'.7z': 'application/x-7z-compressed ',
|
|
74
|
-
'default': 'application/octet-stream'
|
|
75
|
-
}
|
package/src/content.js
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
const contentTypes = require( './content-types.js' )
|
|
2
|
-
const { parseQuery } = require( "./url" );
|
|
3
|
-
let defaultHandler = {
|
|
4
|
-
deserialize: o => {
|
|
5
|
-
try {
|
|
6
|
-
return JSON.parse( o && o.toString() )
|
|
7
|
-
} catch( e ) {
|
|
8
|
-
return o
|
|
9
|
-
}
|
|
10
|
-
},
|
|
11
|
-
serialize: o => {
|
|
12
|
-
if( typeof o === 'string' ) {
|
|
13
|
-
return {
|
|
14
|
-
contentType: 'text/plain',
|
|
15
|
-
data: o
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
if( o instanceof Buffer ) {
|
|
19
|
-
return {
|
|
20
|
-
contentType: 'application/octet-stream',
|
|
21
|
-
data: o
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return {
|
|
25
|
-
contentType: 'application/json',
|
|
26
|
-
data: JSON.stringify( o )
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
let toFormData = ( key, value ) => {
|
|
32
|
-
if( Array.isArray( value ) ) {
|
|
33
|
-
return value.map( toFormData ).flat()
|
|
34
|
-
} else if( typeof value === 'object' ) {
|
|
35
|
-
return Object.keys( value ).map( k => toFormData( `${key}.${k}`, value[k] ) ).flat()
|
|
36
|
-
} else {
|
|
37
|
-
return `${encodeURIComponent( key )}=${encodeURIComponent( value )}`
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const contentHandlers = {
|
|
42
|
-
'application/json': {
|
|
43
|
-
deserialize: s => JSON.parse( s && s.toString() ),
|
|
44
|
-
serialize: o => JSON.stringify( o )
|
|
45
|
-
},
|
|
46
|
-
'text/plain': {
|
|
47
|
-
deserialize: s => s && s.toString(),
|
|
48
|
-
serialize: o => o && o.toString()
|
|
49
|
-
},
|
|
50
|
-
'application/octet-stream': defaultHandler,
|
|
51
|
-
'application/x-www-form-urlencoded': {
|
|
52
|
-
deserialize: s => s && parseQuery( s.toString(), true ),
|
|
53
|
-
serialize: o => Object.keys( o ).map( toFormData ).flat().join( '&' )
|
|
54
|
-
},
|
|
55
|
-
'*/*': defaultHandler,
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
let _acceptsDefault = '*/*'
|
|
59
|
-
|
|
60
|
-
function getHandler( contentType ) {
|
|
61
|
-
if( !contentType ) return contentHandlers[_acceptsDefault]
|
|
62
|
-
//content-type is singular https://greenbytes.de/tech/webdav/rfc2616.html#rfc.section.14.17
|
|
63
|
-
let handler = contentHandlers[contentType];
|
|
64
|
-
if( !handler && contentType.indexOf( ';' ) > -1 ) {
|
|
65
|
-
handler = contentHandlers[contentType.split( ';' )[0].trim()]
|
|
66
|
-
}
|
|
67
|
-
if( handler && typeof handler ) {
|
|
68
|
-
if( typeof handler.serialize !== 'function' ) {
|
|
69
|
-
throw new Error( `Content handlers must provide a serialize function. ${handler}` )
|
|
70
|
-
}
|
|
71
|
-
if( typeof handler.deserialize !== 'function' ) {
|
|
72
|
-
throw new Error( `Content handlers must provide a deserialize function. ${handler}` )
|
|
73
|
-
}
|
|
74
|
-
return handler
|
|
75
|
-
}
|
|
76
|
-
return contentHandlers[_acceptsDefault]
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function getContentTypeByExtension( name, staticContentTypes ) {
|
|
80
|
-
const extension = name.indexOf( '.' ) > -1 ? name.slice( name.lastIndexOf( '.' ) ).toLowerCase() : 'default'
|
|
81
|
-
let contentType = staticContentTypes && staticContentTypes[extension] || null
|
|
82
|
-
|
|
83
|
-
return contentType ? contentType : contentTypes[extension]
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
module.exports = {
|
|
87
|
-
serialize( content, contentType ) {
|
|
88
|
-
return getHandler( contentType && contentType.toLowerCase() ).serialize( content )
|
|
89
|
-
},
|
|
90
|
-
deserialize( content, contentType ) {
|
|
91
|
-
return getHandler( contentType && contentType.toLowerCase() ).deserialize( content )
|
|
92
|
-
},
|
|
93
|
-
initContentHandlers( handlers, acceptsDefault ) {
|
|
94
|
-
Object.assign( handlers, contentHandlers )
|
|
95
|
-
_acceptsDefault = acceptsDefault
|
|
96
|
-
},
|
|
97
|
-
getContentTypeByExtension
|
|
98
|
-
}
|
package/src/decorator.js
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
const cookie = require( 'cookie' )
|
|
2
|
-
const http = require( 'http' )
|
|
3
|
-
const { parseQuery, setMultiValueKey } = require( './url' )
|
|
4
|
-
const log = require( './log' )
|
|
5
|
-
const uuid = require( "uuid" ).v4;
|
|
6
|
-
const { Writable } = require( 'stream' )
|
|
7
|
-
|
|
8
|
-
const setCookie = ( res ) => function() {
|
|
9
|
-
return res.setHeader( 'Set-Cookie', [...( res.getHeader( 'Set-Cookie' ) || [] ), cookie.serialize( ...arguments )] )
|
|
10
|
-
}
|
|
11
|
-
const addressArrayBufferToString = addrBuf => String.fromCharCode.apply( null, new Int8Array( addrBuf ) )
|
|
12
|
-
const excludedMessageProps = {
|
|
13
|
-
setTimeout: true,
|
|
14
|
-
_read: true,
|
|
15
|
-
destroy: true,
|
|
16
|
-
_addHeaderLines: true,
|
|
17
|
-
_addHeaderLine: true,
|
|
18
|
-
_dump: true,
|
|
19
|
-
__proto__: true
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const normalizeHeader = header => header.toLowerCase()
|
|
23
|
-
|
|
24
|
-
const reqProtoProps = () => Object.keys( http.IncomingMessage.prototype ).filter( p => !excludedMessageProps[p] )
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Provide a minimal set of shims to make most middleware, like passport, work
|
|
28
|
-
* @type {{decorateResponse(*=, *=, *): *, decorateRequest(*): *}}
|
|
29
|
-
*/
|
|
30
|
-
module.exports = {
|
|
31
|
-
setCookie,
|
|
32
|
-
decorateRequest( uwsReq, pathParameters, res, {decodeQueryParameters, decodePathParameters, parseCookie} = {} ) {
|
|
33
|
-
//uwsReq can't be used in async functions because it gets de-allocated when the handler function returns
|
|
34
|
-
let req = {}
|
|
35
|
-
//frameworks like passport like to modify the message prototype
|
|
36
|
-
//Setting the prototype of req is not desirable because the entire api of IncomingMessage is not supported
|
|
37
|
-
for( let p of reqProtoProps() ) {
|
|
38
|
-
if( !req[p] ) req[p] = http.IncomingMessage.prototype[p]
|
|
39
|
-
}
|
|
40
|
-
let query = uwsReq.getQuery()
|
|
41
|
-
req.path = uwsReq.getUrl()
|
|
42
|
-
req.url = `${req.path}${query ? '?' + query : ''}`
|
|
43
|
-
req.spliffyUrl = {
|
|
44
|
-
path: uwsReq.getUrl(),
|
|
45
|
-
query: parseQuery( uwsReq.getQuery(), decodeQueryParameters )
|
|
46
|
-
}
|
|
47
|
-
req.spliffyUrl.pathParameters = {}
|
|
48
|
-
if( pathParameters && pathParameters.length > 0 ) {
|
|
49
|
-
for( let i in pathParameters ) {
|
|
50
|
-
req.spliffyUrl.pathParameters[pathParameters[i]] = decodePathParameters
|
|
51
|
-
? decodeURIComponent( uwsReq.getParameter( i ) )
|
|
52
|
-
: uwsReq.getParameter( i )
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
req.params = req.spliffyUrl.pathParameters
|
|
56
|
-
req.headers = {}
|
|
57
|
-
req.method = uwsReq.getMethod().toUpperCase()
|
|
58
|
-
req.remoteAddress = addressArrayBufferToString( res.getRemoteAddressAsText() )
|
|
59
|
-
req.proxiedRemoteAddress = addressArrayBufferToString( res.getProxiedRemoteAddressAsText() )
|
|
60
|
-
uwsReq.forEach( ( header, value ) => setMultiValueKey( req.headers, normalizeHeader( header ), value ) )
|
|
61
|
-
req.get = header => req.headers[normalizeHeader( header )]
|
|
62
|
-
if( parseCookie && req.headers.cookie ) {
|
|
63
|
-
req.cookies = cookie.parse( req.headers.cookie ) || {}
|
|
64
|
-
}
|
|
65
|
-
return req
|
|
66
|
-
},
|
|
67
|
-
decorateResponse( res, req, finalizeResponse, errorTransformer, endError ) {
|
|
68
|
-
res.onAborted( () => {
|
|
69
|
-
res.ended = true
|
|
70
|
-
res.writableEnded = true
|
|
71
|
-
res.finalized = true
|
|
72
|
-
log.error( `Request to ${req.url} was aborted` )
|
|
73
|
-
} )
|
|
74
|
-
|
|
75
|
-
res.headers = {}
|
|
76
|
-
res.headersSent = false
|
|
77
|
-
res.setHeader = ( header, value ) => {
|
|
78
|
-
res.headers[normalizeHeader( header )] = value
|
|
79
|
-
}
|
|
80
|
-
res.removeHeader = header => {
|
|
81
|
-
delete res.headers[normalizeHeader( header )]
|
|
82
|
-
}
|
|
83
|
-
res.flushHeaders = () => {
|
|
84
|
-
if( res.headersSent ) return
|
|
85
|
-
if( !res.statusCode ) res.statusCode = 200
|
|
86
|
-
if( !res.statusMessage ) res.statusMessage = 'OK'
|
|
87
|
-
res.headersSent = true
|
|
88
|
-
res.writeStatus( `${res.statusCode} ${res.statusMessage}` )
|
|
89
|
-
if( typeof res.onFlushHeaders === 'function' ) {
|
|
90
|
-
res.onFlushHeaders( res )
|
|
91
|
-
}
|
|
92
|
-
for( let header of Object.keys( res.headers ) ) {
|
|
93
|
-
if( Array.isArray( res.headers[header] ) ) {
|
|
94
|
-
for( let multiple of res.headers[header] ) {
|
|
95
|
-
res.writeHeader( header, multiple.toString() )
|
|
96
|
-
}
|
|
97
|
-
} else {
|
|
98
|
-
res.writeHeader( header, res.headers[header].toString() )
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
res.writeHead = ( status, headers ) => {
|
|
103
|
-
this.statusCode = status
|
|
104
|
-
res.assignHeaders( headers )
|
|
105
|
-
}
|
|
106
|
-
res.assignHeaders = headers => {
|
|
107
|
-
for( let header of Object.keys( headers ) ) {
|
|
108
|
-
res.headers[normalizeHeader( header )] = headers[header]
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
res.getHeader = header => {
|
|
112
|
-
return res.headers[normalizeHeader( header )]
|
|
113
|
-
}
|
|
114
|
-
res.status = ( code ) => {
|
|
115
|
-
this.statusCode = code
|
|
116
|
-
return this
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function toArrayBuffer( buffer ) {
|
|
120
|
-
return buffer.buffer.slice( buffer.byteOffset, buffer.byteOffset + buffer.byteLength );
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
let outStream
|
|
124
|
-
res.getWritable = () => {
|
|
125
|
-
if( !outStream ) {
|
|
126
|
-
res.streaming = true
|
|
127
|
-
outStream = new Writable( {
|
|
128
|
-
write: (chunk, encoding, cb) => {
|
|
129
|
-
try{
|
|
130
|
-
res.flushHeaders()
|
|
131
|
-
res.write( chunk )
|
|
132
|
-
cb()
|
|
133
|
-
} catch (e) {
|
|
134
|
-
cb(e)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
} )
|
|
138
|
-
.on( 'finish', res.end )
|
|
139
|
-
.on( 'end', res.end )
|
|
140
|
-
.on( 'error', e => {
|
|
141
|
-
try {
|
|
142
|
-
outStream.destroy()
|
|
143
|
-
} finally {
|
|
144
|
-
endError( res, e, uuid(), errorTransformer )
|
|
145
|
-
}
|
|
146
|
-
} )
|
|
147
|
-
}
|
|
148
|
-
return outStream
|
|
149
|
-
}
|
|
150
|
-
res.writeArrayBuffer = res.write
|
|
151
|
-
res.write = chunk => {
|
|
152
|
-
res.streaming = true
|
|
153
|
-
res.flushHeaders()
|
|
154
|
-
if( chunk instanceof Buffer ) {
|
|
155
|
-
res.writeArrayBuffer( toArrayBuffer( chunk ) )
|
|
156
|
-
} else if( typeof chunk === 'string' ) {
|
|
157
|
-
res.writeArrayBuffer( toArrayBuffer( Buffer.from( chunk, 'utf8' ) ) )
|
|
158
|
-
} else {
|
|
159
|
-
res.writeArrayBuffer( toArrayBuffer( Buffer.from( JSON.stringify( chunk ), 'utf8' ) ) )
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const uwsEnd = res.end;
|
|
164
|
-
res.ended = false
|
|
165
|
-
res.end = body => {
|
|
166
|
-
if( res.ended ) {
|
|
167
|
-
return
|
|
168
|
-
}
|
|
169
|
-
res.ended = true
|
|
170
|
-
//provide writableEnded like node does, with slightly different behavior
|
|
171
|
-
if( !res.writableEnded ) {
|
|
172
|
-
res.writableEnded = true
|
|
173
|
-
res.flushHeaders()
|
|
174
|
-
uwsEnd.call( res, body )
|
|
175
|
-
}
|
|
176
|
-
if( typeof res.onEnd === 'function' ) {
|
|
177
|
-
res.onEnd()
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
res.redirect = function( code, location ) {
|
|
182
|
-
if( arguments.length === 1 ) {
|
|
183
|
-
location = code
|
|
184
|
-
code = 301
|
|
185
|
-
}
|
|
186
|
-
return finalizeResponse( req, res, {
|
|
187
|
-
statusCode: code,
|
|
188
|
-
headers: {
|
|
189
|
-
'location': location
|
|
190
|
-
}
|
|
191
|
-
} )
|
|
192
|
-
}
|
|
193
|
-
res.send = ( body ) => {
|
|
194
|
-
finalizeResponse( req, res, body )
|
|
195
|
-
}
|
|
196
|
-
res.json = res.send
|
|
197
|
-
res.setCookie = setCookie( res )
|
|
198
|
-
res.cookie = res.setCookie
|
|
199
|
-
return res
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
}
|