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