@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 CHANGED
File without changes
package/README.md CHANGED
File without changes
package/package.json CHANGED
@@ -1,20 +1,26 @@
1
1
  {
2
2
  "name": "@srfnstack/spliffy",
3
- "version": "0.7.0",
4
- "author": "Robert Kempton <r@snow87.com>",
3
+ "version": "0.9.0",
4
+ "author": "snowbldr",
5
5
  "private": false,
6
6
  "homepage": "https://github.com/narcolepticsnowman/spliffy",
7
7
  "license": "MIT",
8
+ "type": "module",
8
9
  "files": [
9
10
  "src/*",
10
11
  "LICENSE.txt",
11
12
  "README.md"
12
13
  ],
13
- "main": "src/index.js",
14
+ "main": "src/index.mjs",
14
15
  "repository": {
15
16
  "type": "git",
16
17
  "url": "git@github.com:narcolepticsnowman/spliffy.git"
17
18
  },
19
+ "scripts": {
20
+ "test": "npm run lint:fix && jest",
21
+ "lint": "standard --env jest src ./*js && standard --env jest --env browser --global Prism docs example",
22
+ "lint:fix": "standard --env jest --fix src ./*js && standard --env jest --env browser --global Prism --fix docs example"
23
+ },
18
24
  "keywords": [
19
25
  "node",
20
26
  "http",
@@ -24,12 +30,15 @@
24
30
  "rest"
25
31
  ],
26
32
  "dependencies": {
27
- "cookie": "^0.4.0",
33
+ "cookie": "^0.4.1",
28
34
  "etag": "^1.8.1",
29
- "uWebSockets.js": "uNetworking/uWebSockets.js#v19.3.0",
30
- "uuid": "^3.3.3"
35
+ "uWebSockets.js": "uNetworking/uWebSockets.js#v20.4.0",
36
+ "uuid": "^8.3.2"
31
37
  },
32
38
  "devDependencies": {
33
- "helmet": "^4.6.0"
39
+ "standard": "^16.0.4",
40
+ "helmet": "^4.6.0",
41
+ "jest": "^27.3.1",
42
+ "node-fetch": "^2.6.6"
34
43
  }
35
44
  }
@@ -0,0 +1,75 @@
1
+ export default {
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
+ }
@@ -0,0 +1,97 @@
1
+ import contentTypes from './content-types.mjs'
2
+ import { parseQuery } from './url.mjs'
3
+
4
+ const defaultHandler = {
5
+ deserialize: o => {
6
+ if (!o) return o
7
+ try {
8
+ return JSON.parse(o && o.toString())
9
+ } catch (e) {
10
+ return o
11
+ }
12
+ },
13
+ serialize: o => {
14
+ if (!o) return { data: o }
15
+ if (typeof o === 'string') {
16
+ return {
17
+ contentType: 'text/plain',
18
+ data: o
19
+ }
20
+ }
21
+ if (o instanceof Buffer) {
22
+ return {
23
+ contentType: 'application/octet-stream',
24
+ data: o
25
+ }
26
+ }
27
+ return {
28
+ contentType: 'application/json',
29
+ data: JSON.stringify(o)
30
+ }
31
+ }
32
+ }
33
+
34
+ const toFormData = (key, value) => {
35
+ if (Array.isArray(value)) {
36
+ return value.map(toFormData).flat()
37
+ } else if (typeof value === 'object') {
38
+ return Object.keys(value).map(k => toFormData(`${key}.${k}`, value[k])).flat()
39
+ } else {
40
+ return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
41
+ }
42
+ }
43
+
44
+ const contentHandlers = {
45
+ 'application/json': {
46
+ deserialize: s => s && JSON.parse(s && s.toString()),
47
+ serialize: o => o && JSON.stringify(o)
48
+ },
49
+ 'text/plain': {
50
+ deserialize: s => s && s.toString(),
51
+ serialize: o => o && o.toString()
52
+ },
53
+ 'application/octet-stream': defaultHandler,
54
+ 'application/x-www-form-urlencoded': {
55
+ deserialize: s => s && parseQuery(s.toString(), true),
56
+ serialize: o => o && Object.keys(o).map(toFormData).flat().join('&')
57
+ },
58
+ '*/*': defaultHandler
59
+ }
60
+
61
+ function getHandler (contentType, acceptsDefault) {
62
+ if (!contentType) return contentHandlers[acceptsDefault]
63
+ // content-type is singular https://greenbytes.de/tech/webdav/rfc2616.html#rfc.section.14.17
64
+ let handler = contentHandlers[contentType]
65
+ if (!handler && contentType.indexOf(';') > -1) {
66
+ handler = contentHandlers[contentType.split(';')[0].trim()]
67
+ }
68
+ if (handler && typeof handler) {
69
+ if (typeof handler.serialize !== 'function') {
70
+ throw new Error(`Content handlers must provide a serialize function. ${handler}`)
71
+ }
72
+ if (typeof handler.deserialize !== 'function') {
73
+ throw new Error(`Content handlers must provide a deserialize function. ${handler}`)
74
+ }
75
+ return handler
76
+ }
77
+ return contentHandlers[acceptsDefault]
78
+ }
79
+
80
+ export function getContentTypeByExtension (name, staticContentTypes) {
81
+ const extension = name.indexOf('.') > -1 ? name.slice(name.lastIndexOf('.')).toLowerCase() : 'default'
82
+ const contentType = staticContentTypes?.[extension] || null
83
+
84
+ return contentType || contentTypes[extension]
85
+ }
86
+
87
+ export function serializeBody (content, contentType, acceptsDefault) {
88
+ return getHandler(contentType && contentType.toLowerCase(), acceptsDefault).serialize(content)
89
+ }
90
+
91
+ export function deserializeBody (content, contentType, acceptsDefault) {
92
+ return getHandler(contentType && contentType.toLowerCase(), acceptsDefault).deserialize(content)
93
+ }
94
+
95
+ export function initContentHandlers (handlers) {
96
+ return Object.assign({}, contentHandlers, handlers)
97
+ }
@@ -0,0 +1,212 @@
1
+ import cookie from 'cookie'
2
+ import http from 'http'
3
+ import { parseQuery } from './url.mjs'
4
+ import log from './log.mjs'
5
+ import { v4 as uuid } from 'uuid'
6
+ import stream from 'stream'
7
+ import httpStatusCodes, { defaultStatusMessages } from './httpStatusCodes.mjs'
8
+
9
+ const { Writable } = stream
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
+ export const setCookie = (res) => function () {
27
+ return res.setHeader('Set-Cookie', [...(res.getHeader('Set-Cookie') || []), cookie.serialize(...arguments)])
28
+ }
29
+
30
+ export function decorateRequest (uwsReq, pathParameters, res, {
31
+ decodeQueryParameters,
32
+ decodePathParameters,
33
+ parseCookie,
34
+ extendIncomingMessage
35
+ } = {}) {
36
+ // uwsReq can't be used in async functions because it gets de-allocated when the handler function returns
37
+ const req = {}
38
+ if (extendIncomingMessage) {
39
+ // frameworks like passport like to modify the message prototype
40
+ // Setting the prototype of req is not desirable because the entire api of IncomingMessage is not supported
41
+ for (const p of reqProtoProps()) {
42
+ if (!req[p]) req[p] = http.IncomingMessage.prototype[p]
43
+ }
44
+ }
45
+ const query = uwsReq.getQuery()
46
+ req.path = uwsReq.getUrl()
47
+ req.url = `${req.path}${query ? '?' + query : ''}`
48
+ req.spliffyUrl = {
49
+ path: req.path,
50
+ query: query && parseQuery(query, decodeQueryParameters),
51
+ pathParameters: {}
52
+ }
53
+ if (pathParameters && pathParameters.length > 0) {
54
+ for (const i in pathParameters) {
55
+ req.spliffyUrl.pathParameters[pathParameters[i]] =
56
+ decodePathParameters
57
+ ? decodeURIComponent(uwsReq.getParameter(i))
58
+ : uwsReq.getParameter(i)
59
+ }
60
+ }
61
+ req.params = req.spliffyUrl.pathParameters
62
+ req.headers = {}
63
+ uwsReq.forEach((header, value) => { req.headers[header] = value })
64
+ req.method = uwsReq.getMethod().toUpperCase()
65
+ req.remoteAddress = addressArrayBufferToString(res.getRemoteAddressAsText())
66
+ req.proxiedRemoteAddress = addressArrayBufferToString(res.getProxiedRemoteAddressAsText())
67
+ req.get = header => req.headers[header]
68
+ if (parseCookie && req.headers.cookie) {
69
+ req.cookies = cookie.parse(req.headers.cookie) || {}
70
+ }
71
+ return req
72
+ }
73
+
74
+ function toArrayBuffer (buffer) {
75
+ return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)
76
+ }
77
+
78
+ export function decorateResponse (res, req, finalizeResponse, errorTransformer, endError, { acceptsDefault }) {
79
+ res.onAborted(() => {
80
+ res.ended = true
81
+ res.writableEnded = true
82
+ res.finalized = true
83
+ log.error(`Request to ${req.url} was aborted`)
84
+ })
85
+ res.acceptsDefault = acceptsDefault
86
+ res.headers = {}
87
+ res.headersSent = false
88
+ res.setHeader = (header, value) => {
89
+ res.headers[normalizeHeader(header)] = value
90
+ }
91
+ res.removeHeader = header => {
92
+ delete res.headers[normalizeHeader(header)]
93
+ }
94
+ res.flushHeaders = () => {
95
+ if (res.headersSent) return
96
+ if (!res.statusCode) res.statusCode = httpStatusCodes.OK
97
+ if (!res.statusMessage) res.statusMessage = defaultStatusMessages[res.statusCode]
98
+ res.headersSent = true
99
+ res.writeStatus(`${res.statusCode} ${res.statusMessage}`)
100
+ if (typeof res.onFlushHeaders === 'function') {
101
+ res.onFlushHeaders(res)
102
+ }
103
+ for (const header of Object.keys(res.headers)) {
104
+ if (Array.isArray(res.headers[header])) {
105
+ for (const multiple of res.headers[header]) {
106
+ res.writeHeader(header, multiple.toString())
107
+ }
108
+ } else {
109
+ res.writeHeader(header, res.headers[header].toString())
110
+ }
111
+ }
112
+ }
113
+ res.writeHead = (status, headers) => {
114
+ res.statusCode = status
115
+ res.assignHeaders(headers)
116
+ }
117
+ res.assignHeaders = headers => {
118
+ for (const header of Object.keys(headers)) {
119
+ res.headers[normalizeHeader(header)] = headers[header]
120
+ }
121
+ }
122
+ res.getHeader = header => {
123
+ return res.headers[normalizeHeader(header)]
124
+ }
125
+ res.status = (code) => {
126
+ res.statusCode = code
127
+ return this
128
+ }
129
+
130
+ res.writeArrayBuffer = res.write
131
+ res.write = (chunk, encoding, cb) => {
132
+ try {
133
+ res.streaming = true
134
+ res.flushHeaders()
135
+ let result
136
+ if (chunk instanceof Buffer) {
137
+ result = res.writeArrayBuffer(toArrayBuffer(chunk))
138
+ } else if (typeof chunk === 'string') {
139
+ result = res.writeArrayBuffer(toArrayBuffer(Buffer.from(chunk, encoding || 'utf8')))
140
+ } else {
141
+ result = res.writeArrayBuffer(toArrayBuffer(Buffer.from(JSON.stringify(chunk), encoding || 'utf8')))
142
+ }
143
+ if (typeof cb === 'function') {
144
+ cb()
145
+ }
146
+ return result
147
+ } catch (e) {
148
+ if (typeof cb === 'function') {
149
+ cb(e)
150
+ } else {
151
+ throw e
152
+ }
153
+ }
154
+ }
155
+ let outStream
156
+ res.getWritable = () => {
157
+ if (!outStream) {
158
+ res.streaming = true
159
+ outStream = new Writable({
160
+ write: res.write
161
+ })
162
+ .on('finish', res.end)
163
+ .on('end', res.end)
164
+ .on('error', e => {
165
+ try {
166
+ outStream.destroy()
167
+ } finally {
168
+ endError(res, e, uuid(), errorTransformer)
169
+ }
170
+ })
171
+ }
172
+ return outStream
173
+ }
174
+
175
+ const uwsEnd = res.end
176
+ res.ended = false
177
+ res.end = body => {
178
+ if (res.ended) {
179
+ return
180
+ }
181
+ // provide writableEnded like node does, with slightly different behavior
182
+ if (!res.writableEnded) {
183
+ res.flushHeaders()
184
+ uwsEnd.call(res, body)
185
+ res.writableEnded = true
186
+ res.ended = true
187
+ }
188
+ if (typeof res.onEnd === 'function') {
189
+ res.onEnd()
190
+ }
191
+ }
192
+
193
+ res.redirect = function (code, location) {
194
+ if (arguments.length === 1) {
195
+ location = code
196
+ code = httpStatusCodes.MOVED_PERMANENTLY
197
+ }
198
+ return finalizeResponse(req, res, {
199
+ statusCode: code,
200
+ headers: {
201
+ location: location
202
+ }
203
+ })
204
+ }
205
+ res.send = (body) => {
206
+ finalizeResponse(req, res, body)
207
+ }
208
+ res.json = res.send
209
+ res.setCookie = setCookie(res)
210
+ res.cookie = res.setCookie
211
+ return res
212
+ }
@@ -0,0 +1,249 @@
1
+ import log from './log.mjs'
2
+ import { deserializeBody, serializeBody } from './content.mjs'
3
+ import { invokeMiddleware } from './middleware.mjs'
4
+ import { decorateResponse, decorateRequest } from './decorator.mjs'
5
+ import { v4 as uuid } from 'uuid'
6
+ import stream from 'stream'
7
+ const { Readable } = 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 bodyPromise The request body promise
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, bodyPromise, handler, middleware, errorTransformer) => {
20
+ try {
21
+ bodyPromise = bodyPromise.then(bodyContent => {
22
+ if (bodyContent instanceof Readable) return bodyContent
23
+ if (res.writableEnded) return
24
+ return deserializeBody(bodyContent, req.headers['content-type'], res.acceptsDefault)
25
+ })
26
+ } catch (e) {
27
+ log.error('Failed to parse request.', e)
28
+ end(res, 400, handler.statusCodeOverride)
29
+ return
30
+ }
31
+
32
+ try {
33
+ const handled = await handler({ url, bodyPromise, headers: req.headers, req, res })
34
+ finalizeResponse(req, res, handled, handler.statusCodeOverride)
35
+ } catch (e) {
36
+ const refId = uuid()
37
+ log.error('handler failed', e, refId)
38
+ await executeMiddleware(middleware, req, res, errorTransformer, refId, e)
39
+ endError(res, e, refId, errorTransformer)
40
+ }
41
+ }
42
+
43
+ const endError = (res, e, refId, errorTransformer) => {
44
+ if (e.body && typeof e.body !== 'string') {
45
+ e.body = JSON.stringify(e.body)
46
+ }
47
+ if (typeof errorTransformer === 'function') {
48
+ e = errorTransformer(e, refId)
49
+ }
50
+ res.headers['x-ref-id'] = refId
51
+ end(res, e.statusCode || 500, null, e.body || '')
52
+ }
53
+
54
+ const end = (res, defaultStatusCode, statusCodeOverride, body) => {
55
+ // status set directly on res wins
56
+ res.statusCode = statusCodeOverride || res.statusCode || defaultStatusCode
57
+ if (body instanceof Readable || res.streaming) {
58
+ res.streaming = true
59
+ if (body instanceof Readable) {
60
+ res.flushHeaders()
61
+ pipeResponse(res, body)
62
+ }
63
+ // handler is responsible for ending the response if they are streaming
64
+ } else {
65
+ res.end(doSerializeBody(body, res) || '')
66
+ }
67
+ }
68
+
69
+ const writeAccess = function (req, res) {
70
+ const start = new Date().getTime()
71
+ return () => {
72
+ log.access(req.remoteAddress, res.proxiedRemoteAddress || '', res.statusCode, req.method, req.url, new Date().getTime() - start + 'ms')
73
+ }
74
+ }
75
+
76
+ const finalizeResponse = (req, res, handled, statusCodeOverride) => {
77
+ if (!res.finalized) {
78
+ if (!handled) {
79
+ // 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
80
+ end(res, 200, statusCodeOverride)
81
+ } else {
82
+ // if the returned object has known fields, treat it as a response object instead of the body
83
+ if (handled.body || handled.statusMessage || handled.statusCode || handled.headers) {
84
+ if (handled.headers) {
85
+ res.assignHeaders(handled.headers)
86
+ }
87
+ res.statusMessage = handled.statusMessage || res.statusMessage
88
+ end(res, handled.statusCode || 200, statusCodeOverride, handled.body)
89
+ } else {
90
+ end(res, 200, statusCodeOverride, handled)
91
+ }
92
+ }
93
+ res.finalized = true
94
+ }
95
+ }
96
+
97
+ const pipeResponse = (res, readStream, errorTransformer) => {
98
+ readStream.on('data', res.write)
99
+ .on('end', res.end)
100
+ .on('error', e => {
101
+ try {
102
+ readStream.destroy()
103
+ } finally {
104
+ endError(res, e, uuid(), errorTransformer)
105
+ }
106
+ })
107
+ }
108
+
109
+ const doSerializeBody = (body, res) => {
110
+ if (!body || typeof body === 'string' || body instanceof Readable) {
111
+ return body
112
+ }
113
+ const contentType = res.getHeader('content-type')
114
+ const serialized = serializeBody(body, contentType, res.acceptsDefault)
115
+
116
+ if (serialized?.contentType && !contentType) {
117
+ res.headers['content-type'] = serialized.contentType
118
+ }
119
+ return serialized?.data || ''
120
+ }
121
+
122
+ async function executeMiddleware (middleware, req, res, errorTransformer, refId, e) {
123
+ if (!middleware) return
124
+
125
+ let applicableMiddleware = middleware[req.method]
126
+ if (middleware.ALL) {
127
+ if (applicableMiddleware) applicableMiddleware = middleware.ALL.concat(applicableMiddleware)
128
+ else applicableMiddleware = middleware.ALL
129
+ }
130
+
131
+ if (!applicableMiddleware || applicableMiddleware.length === 0) {
132
+ return
133
+ }
134
+ if (e) {
135
+ await invokeMiddleware(applicableMiddleware.filter(mw => mw.length === 4), req, res, e)
136
+ } else {
137
+ await invokeMiddleware(applicableMiddleware.filter(mw => mw.length === 3), req, res)
138
+ }
139
+ }
140
+
141
+ const handleRequest = async (req, res, handler, middleware, errorTransformer) => {
142
+ try {
143
+ let reqBody
144
+ if (!handler.streamRequestBody) {
145
+ let buffer
146
+ reqBody = new Promise(
147
+ resolve =>
148
+ res.onData(async (data, isLast) => {
149
+ if (isLast) {
150
+ buffer = data.byteLength > 0 ? Buffer.concat([buffer, Buffer.from(data)].filter(b => b)) : buffer
151
+ resolve(buffer)
152
+ }
153
+ buffer = Buffer.concat([buffer, Buffer.from(data)].filter(b => b))
154
+ })
155
+ )
156
+ } else {
157
+ const readable = new Readable({
158
+ read: () => {
159
+ }
160
+ })
161
+ res.onData(async (data, isLast) => {
162
+ if (data.byteLength === 0 && !isLast) return
163
+ // data must be copied so it isn't lost
164
+ readable.push(Buffer.concat([Buffer.from(data)]))
165
+ if (isLast) {
166
+ readable.push(null)
167
+ }
168
+ })
169
+ reqBody = Promise.resolve(readable)
170
+ }
171
+ await executeMiddleware(middleware, req, res, errorTransformer)
172
+ if (!res.writableEnded && !res.ended) {
173
+ await executeHandler(req.spliffyUrl, res, req, reqBody, handler, middleware, errorTransformer)
174
+ }
175
+ } catch (e) {
176
+ const refId = uuid()
177
+ log.error('Handling request failed', e, refId)
178
+ await executeMiddleware(middleware, req, res, errorTransformer, refId, e)
179
+ if (!res.writableEnded) { endError(res, e, refId, errorTransformer) }
180
+ }
181
+ }
182
+
183
+ export const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD', 'CONNECT', 'TRACE']
184
+
185
+ let currentDate = new Date().toISOString()
186
+ setInterval(() => { currentDate = new Date().toISOString() }, 1000)
187
+
188
+ export const createHandler = (handler, middleware, pathParameters, config) => function (res, req) {
189
+ try {
190
+ req = decorateRequest(req, pathParameters, res, config)
191
+ res = decorateResponse(res, req, finalizeResponse, config.errorTransformer, endError, config)
192
+
193
+ if (config.logAccess) {
194
+ res.onEnd = writeAccess(req, res)
195
+ }
196
+
197
+ if (config.writeDateHeader) {
198
+ res.headers.date = currentDate
199
+ }
200
+
201
+ handleRequest(req, res, handler, middleware, config.errorTransformer)
202
+ .catch(e => {
203
+ log.error('Failed handling request', e)
204
+ res.statusCode = 500
205
+ res.end()
206
+ })
207
+ } catch (e) {
208
+ log.error('Failed handling request', e)
209
+ res.statusCode = 500
210
+ res.end()
211
+ }
212
+ }
213
+
214
+ export const createNotFoundHandler = config => {
215
+ const handler = config.defaultRouteHandler || config.notFoundRouteHandler
216
+ const params = handler?.pathParameters || []
217
+ return (res, req) => {
218
+ try {
219
+ req = decorateRequest(req, params, res, config)
220
+ res = decorateResponse(res, req, finalizeResponse, config.errorTransformer, endError, config)
221
+ if (config.logAccess) {
222
+ res.onEnd = writeAccess(req, res)
223
+ }
224
+ if (handler && typeof handler === 'object') {
225
+ if (handler.handlers && typeof handler.handlers[req.method] === 'function') {
226
+ if ('statusCodeOverride' in handler) {
227
+ handler.handlers[req.method].statusCodeOverride = handler.statusCodeOverride
228
+ }
229
+ handleRequest(req, res,
230
+ handler.handlers[req.method],
231
+ handler.middleware,
232
+ config.errorTransformer
233
+ ).catch((e) => {
234
+ log.error('Unexpected exception during request handling', e)
235
+ res.statusCode = 500
236
+ })
237
+ } else {
238
+ res.statusCode = 405
239
+ res.end()
240
+ }
241
+ } else {
242
+ res.statusCode = 404
243
+ res.end()
244
+ }
245
+ } catch (e) {
246
+ log.error('Failed handling request', e)
247
+ }
248
+ }
249
+ }