@srfnstack/spliffy 1.2.8 → 1.3.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/package.json +5 -3
- package/src/decorator.mjs +107 -113
- package/src/handler.mjs +105 -80
- package/src/middleware.mjs +30 -17
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@srfnstack/spliffy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"author": "snowbldr",
|
|
5
5
|
"private": false,
|
|
6
6
|
"homepage": "https://github.com/SRFNStack/spliffy",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"engines": {
|
|
10
|
-
"node": ">=
|
|
10
|
+
"node": ">=20 <=24"
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"src/*",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"scripts": {
|
|
23
23
|
"test": "npm run lint:fix && jest",
|
|
24
24
|
"lint": "standard --env jest src ./*js && standard --env jest --env browser --global Prism docs example",
|
|
25
|
-
"lint:fix": "standard --env jest --fix src ./*js && standard --env jest --env browser --global Prism --fix docs example"
|
|
25
|
+
"lint:fix": "standard --env jest --fix src ./*js && standard --env jest --env browser --global Prism --fix docs example",
|
|
26
|
+
"bench": "node bench/compare.mjs"
|
|
26
27
|
},
|
|
27
28
|
"keywords": [
|
|
28
29
|
"node",
|
|
@@ -39,6 +40,7 @@
|
|
|
39
40
|
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.56.0"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
43
|
+
"autocannon": "^8.0.0",
|
|
42
44
|
"helmet": "^4.6.0",
|
|
43
45
|
"jest": "^27.3.1",
|
|
44
46
|
"node-fetch": "^2.6.7",
|
package/src/decorator.mjs
CHANGED
|
@@ -8,7 +8,8 @@ import httpStatusCodes, { defaultStatusMessages } from './httpStatusCodes.mjs'
|
|
|
8
8
|
|
|
9
9
|
const { Writable } = stream
|
|
10
10
|
|
|
11
|
-
const addressArrayBufferToString = addrBuf =>
|
|
11
|
+
const addressArrayBufferToString = addrBuf => Buffer.from(addrBuf).toString()
|
|
12
|
+
|
|
12
13
|
const excludedMessageProps = {
|
|
13
14
|
setTimeout: true,
|
|
14
15
|
_read: true,
|
|
@@ -19,73 +20,78 @@ const excludedMessageProps = {
|
|
|
19
20
|
__proto__: true
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
const normalizeHeader = header => header.toLowerCase()
|
|
23
|
-
|
|
24
23
|
const reqProtoProps = () => Object.keys(http.IncomingMessage.prototype).filter(p => !excludedMessageProps[p])
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
class SpliffyRequest {
|
|
26
|
+
constructor (uwsReq, pathParameters, res, config) {
|
|
27
|
+
const query = uwsReq.getQuery()
|
|
28
|
+
this.res = res
|
|
29
|
+
this.path = uwsReq.getUrl()
|
|
30
|
+
this.url = query ? `${this.path}?${query}` : this.path
|
|
31
|
+
this.method = uwsReq.getMethod().toUpperCase()
|
|
32
|
+
this.headers = {}
|
|
33
|
+
uwsReq.forEach((header, value) => {
|
|
34
|
+
this.headers[header] = value
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
if (config.extendIncomingMessage) {
|
|
38
|
+
for (const p of reqProtoProps()) {
|
|
39
|
+
if (!this[p]) this[p] = http.IncomingMessage.prototype[p]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const paramToIndex = pathParameters.reduce((acc, cur, i) => {
|
|
44
|
+
acc[cur] = i
|
|
45
|
+
return acc
|
|
46
|
+
}, {})
|
|
29
47
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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]
|
|
48
|
+
this.spliffyUrl = {
|
|
49
|
+
path: this.path,
|
|
50
|
+
query: (query && parseQuery(query, config.decodeQueryParameters)) || {},
|
|
51
|
+
param: name => uwsReq.getParameter(paramToIndex[name])
|
|
43
52
|
}
|
|
53
|
+
this.query = this.spliffyUrl.query
|
|
54
|
+
if (config.parseCookie) {
|
|
55
|
+
this.cookies = (this.headers.cookie && cookie.parse(this.headers.cookie)) || {}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get remoteAddress () {
|
|
60
|
+
const val = addressArrayBufferToString(this.res.getRemoteAddressAsText())
|
|
61
|
+
Object.defineProperty(this, 'remoteAddress', { value: val, enumerable: true })
|
|
62
|
+
return val
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get proxiedRemoteAddress () {
|
|
66
|
+
const val = addressArrayBufferToString(this.res.getProxiedRemoteAddressAsText())
|
|
67
|
+
Object.defineProperty(this, 'proxiedRemoteAddress', { value: val, enumerable: true })
|
|
68
|
+
return val
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get (header) {
|
|
72
|
+
return this.headers[header.toLowerCase()]
|
|
44
73
|
}
|
|
45
|
-
const query = uwsReq.getQuery()
|
|
46
|
-
req.path = uwsReq.getUrl()
|
|
47
|
-
req.url = `${req.path}${query ? '?' + query : ''}`
|
|
48
|
-
const paramToIndex = pathParameters.reduce((acc, cur, i) => {
|
|
49
|
-
acc[cur] = i
|
|
50
|
-
return acc
|
|
51
|
-
}, {})
|
|
52
|
-
req.spliffyUrl = {
|
|
53
|
-
path: req.path,
|
|
54
|
-
query: (query && parseQuery(query, decodeQueryParameters)) || {},
|
|
55
|
-
param: name => uwsReq.getParameter(paramToIndex[name])
|
|
56
|
-
}
|
|
57
|
-
req.query = req.spliffyUrl.query
|
|
58
|
-
req.headers = {}
|
|
59
|
-
uwsReq.forEach((header, value) => { req.headers[header] = value })
|
|
60
|
-
req.method = uwsReq.getMethod().toUpperCase()
|
|
61
|
-
req.remoteAddress = addressArrayBufferToString(res.getRemoteAddressAsText())
|
|
62
|
-
req.proxiedRemoteAddress = addressArrayBufferToString(res.getProxiedRemoteAddressAsText())
|
|
63
|
-
req.get = header => req.headers[header]
|
|
64
|
-
if (parseCookie) {
|
|
65
|
-
req.cookies = (req.headers.cookie && cookie.parse(req.headers.cookie)) || {}
|
|
66
|
-
}
|
|
67
|
-
return req
|
|
68
74
|
}
|
|
69
75
|
|
|
70
|
-
function
|
|
71
|
-
return
|
|
76
|
+
export function decorateRequest (uwsReq, pathParameters, res, config) {
|
|
77
|
+
return new SpliffyRequest(uwsReq, pathParameters, res, config)
|
|
72
78
|
}
|
|
73
79
|
|
|
74
|
-
export function decorateResponse (res, req, finalizeResponse, errorTransformer, endError,
|
|
80
|
+
export function decorateResponse (res, req, finalizeResponse, errorTransformer, endError, config) {
|
|
75
81
|
res.onAborted(() => {
|
|
76
82
|
res.ended = true
|
|
77
83
|
res.writableEnded = true
|
|
78
84
|
res.finalized = true
|
|
79
85
|
log.error(`Request to ${req.url} was aborted`)
|
|
80
86
|
})
|
|
81
|
-
res.acceptsDefault = acceptsDefault
|
|
87
|
+
res.acceptsDefault = config.acceptsDefault
|
|
82
88
|
res.headers = {}
|
|
83
89
|
res.headersSent = false
|
|
84
90
|
res.setHeader = (header, value) => {
|
|
85
|
-
res.headers[
|
|
91
|
+
res.headers[header.toLowerCase()] = value
|
|
86
92
|
}
|
|
87
93
|
res.removeHeader = header => {
|
|
88
|
-
delete res.headers[
|
|
94
|
+
delete res.headers[header.toLowerCase()]
|
|
89
95
|
}
|
|
90
96
|
res.flushHeaders = () => {
|
|
91
97
|
if (res.headersSent) return
|
|
@@ -96,118 +102,106 @@ export function decorateResponse (res, req, finalizeResponse, errorTransformer,
|
|
|
96
102
|
if (typeof res.onFlushHeaders === 'function') {
|
|
97
103
|
res.onFlushHeaders(res)
|
|
98
104
|
}
|
|
99
|
-
for (const header
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
for (const header in res.headers) {
|
|
106
|
+
const val = res.headers[header]
|
|
107
|
+
if (Array.isArray(val)) {
|
|
108
|
+
for (let i = 0; i < val.length; i++) {
|
|
109
|
+
res.writeHeader(header, val[i].toString())
|
|
103
110
|
}
|
|
104
111
|
} else {
|
|
105
|
-
res.writeHeader(header,
|
|
112
|
+
res.writeHeader(header, val.toString())
|
|
106
113
|
}
|
|
107
114
|
}
|
|
108
115
|
}
|
|
109
116
|
res.writeHead = (status, headers) => {
|
|
110
117
|
res.statusCode = status
|
|
111
|
-
res.assignHeaders(headers)
|
|
118
|
+
if (headers) res.assignHeaders(headers)
|
|
112
119
|
}
|
|
113
120
|
res.assignHeaders = headers => {
|
|
114
|
-
for (const header
|
|
115
|
-
res.headers[
|
|
121
|
+
for (const header in headers) {
|
|
122
|
+
res.headers[header.toLowerCase()] = headers[header]
|
|
116
123
|
}
|
|
117
124
|
}
|
|
118
125
|
res.getHeader = header => {
|
|
119
|
-
return res.headers[
|
|
126
|
+
return res.headers[header.toLowerCase()]
|
|
120
127
|
}
|
|
121
128
|
res.status = (code) => {
|
|
122
129
|
res.statusCode = code
|
|
123
|
-
return
|
|
130
|
+
return res
|
|
124
131
|
}
|
|
125
132
|
|
|
126
|
-
|
|
133
|
+
const uwsWrite = res.write
|
|
127
134
|
res.write = (chunk, encoding, cb) => {
|
|
128
135
|
res.cork(() => {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
data = toArrayBuffer(Buffer.from(chunk, encoding || 'utf8'))
|
|
137
|
-
} else {
|
|
138
|
-
data = toArrayBuffer(Buffer.from(JSON.stringify(chunk), encoding || 'utf8'))
|
|
139
|
-
}
|
|
140
|
-
const result = res.uwsWrite(data)
|
|
141
|
-
if (typeof cb === 'function') {
|
|
142
|
-
cb()
|
|
143
|
-
}
|
|
144
|
-
return result
|
|
145
|
-
} catch (e) {
|
|
146
|
-
if (typeof cb === 'function') {
|
|
147
|
-
cb(e)
|
|
148
|
-
} else {
|
|
149
|
-
throw e
|
|
150
|
-
}
|
|
136
|
+
res.streaming = true
|
|
137
|
+
res.flushHeaders()
|
|
138
|
+
let data
|
|
139
|
+
if (chunk instanceof Buffer || chunk instanceof Uint8Array || typeof chunk === 'string') {
|
|
140
|
+
data = chunk
|
|
141
|
+
} else {
|
|
142
|
+
data = JSON.stringify(chunk)
|
|
151
143
|
}
|
|
144
|
+
const result = uwsWrite.call(res, data)
|
|
145
|
+
if (typeof cb === 'function') cb()
|
|
146
|
+
return result
|
|
152
147
|
})
|
|
153
148
|
}
|
|
154
|
-
|
|
149
|
+
|
|
155
150
|
res.getWritable = () => {
|
|
156
|
-
if (!outStream) {
|
|
151
|
+
if (!res.outStream) {
|
|
157
152
|
res.streaming = true
|
|
158
|
-
outStream = new Writable({
|
|
159
|
-
write:
|
|
153
|
+
res.outStream = new Writable({
|
|
154
|
+
write: (chunk, encoding, callback) => {
|
|
155
|
+
res.write(chunk, encoding, callback)
|
|
156
|
+
}
|
|
160
157
|
})
|
|
161
|
-
.on('finish', res.end)
|
|
162
|
-
.on('end', res.end)
|
|
158
|
+
.on('finish', () => res.end())
|
|
163
159
|
.on('error', e => {
|
|
164
160
|
try {
|
|
165
|
-
outStream.destroy()
|
|
161
|
+
res.outStream.destroy()
|
|
166
162
|
} finally {
|
|
167
163
|
endError(res, e, uuid(), errorTransformer)
|
|
168
164
|
}
|
|
169
165
|
})
|
|
170
166
|
}
|
|
171
|
-
return outStream
|
|
167
|
+
return res.outStream
|
|
172
168
|
}
|
|
173
169
|
|
|
174
170
|
const uwsEnd = res.end
|
|
175
171
|
res.ended = false
|
|
176
172
|
res.end = body => {
|
|
177
|
-
if (res.ended)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
res.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
res.writableEnded = true
|
|
186
|
-
res.ended = true
|
|
187
|
-
})
|
|
188
|
-
}
|
|
189
|
-
if (typeof res.onEnd === 'function') {
|
|
190
|
-
res.onEnd()
|
|
191
|
-
}
|
|
173
|
+
if (res.ended) return
|
|
174
|
+
res.cork(() => {
|
|
175
|
+
if (!res.headersSent) res.flushHeaders()
|
|
176
|
+
uwsEnd.call(res, body || '')
|
|
177
|
+
res.writableEnded = true
|
|
178
|
+
res.ended = true
|
|
179
|
+
if (typeof res.onEnd === 'function') res.onEnd()
|
|
180
|
+
})
|
|
192
181
|
}
|
|
193
182
|
|
|
194
|
-
res.redirect =
|
|
195
|
-
if (
|
|
183
|
+
res.redirect = (code, location) => {
|
|
184
|
+
if (typeof code === 'string') {
|
|
196
185
|
location = code
|
|
197
186
|
code = httpStatusCodes.MOVED_PERMANENTLY
|
|
198
187
|
}
|
|
199
188
|
return finalizeResponse(req, res, {
|
|
200
189
|
statusCode: code,
|
|
201
|
-
headers: {
|
|
202
|
-
location: location
|
|
203
|
-
}
|
|
190
|
+
headers: { location }
|
|
204
191
|
})
|
|
205
192
|
}
|
|
206
|
-
res.send = (body) =>
|
|
207
|
-
finalizeResponse(req, res, body)
|
|
208
|
-
}
|
|
193
|
+
res.send = (body) => finalizeResponse(req, res, body)
|
|
209
194
|
res.json = res.send
|
|
210
|
-
res.setCookie =
|
|
195
|
+
res.setCookie = (name, value, options) => {
|
|
196
|
+
const serialized = cookie.serialize(name, value, options)
|
|
197
|
+
const existing = res.getHeader('Set-Cookie')
|
|
198
|
+
if (existing) {
|
|
199
|
+
if (Array.isArray(existing)) existing.push(serialized)
|
|
200
|
+
else res.setHeader('Set-Cookie', [existing, serialized])
|
|
201
|
+
} else {
|
|
202
|
+
res.setHeader('Set-Cookie', serialized)
|
|
203
|
+
}
|
|
204
|
+
}
|
|
211
205
|
res.cookie = res.setCookie
|
|
212
206
|
return res
|
|
213
207
|
}
|
package/src/handler.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import log from './log.mjs'
|
|
2
2
|
import { deserializeBody, serializeBody } from './content.mjs'
|
|
3
|
-
import { invokeMiddleware } from './middleware.mjs'
|
|
3
|
+
import { invokeMiddleware, preProcessMiddleware } from './middleware.mjs'
|
|
4
4
|
import { decorateResponse, decorateRequest } from './decorator.mjs'
|
|
5
5
|
import { v4 as uuid } from 'uuid'
|
|
6
6
|
import stream from 'stream'
|
|
@@ -34,7 +34,9 @@ const executeHandler = async (url, res, req, bodyPromise, handler, middleware, e
|
|
|
34
34
|
finalizeResponse(req, res, handled, handler.statusCodeOverride)
|
|
35
35
|
} catch (e) {
|
|
36
36
|
const refId = uuid()
|
|
37
|
-
|
|
37
|
+
if (middleware) {
|
|
38
|
+
await executeMiddleware(middleware, req, res, errorTransformer, refId, e)
|
|
39
|
+
}
|
|
38
40
|
endError(res, e, refId, errorTransformer)
|
|
39
41
|
}
|
|
40
42
|
}
|
|
@@ -73,9 +75,9 @@ const ipv6CompressRegex = /\b:?(?:0+:?){2,}/g
|
|
|
73
75
|
const compressIpv6 = ip => ip && ip.includes(':') ? ip.replaceAll(ipv6CompressRegex, '::') : ip
|
|
74
76
|
|
|
75
77
|
const writeAccess = function (req, res) {
|
|
76
|
-
const start =
|
|
78
|
+
const start = Date.now()
|
|
77
79
|
return () => {
|
|
78
|
-
log.access(compressIpv6(req.remoteAddress), compressIpv6(res.proxiedRemoteAddress) || '', res.statusCode, req.method, req.url,
|
|
80
|
+
log.access(compressIpv6(req.remoteAddress), compressIpv6(res.proxiedRemoteAddress) || '', res.statusCode, req.method, req.url, Date.now() - start + 'ms')
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
|
|
@@ -101,8 +103,8 @@ const finalizeResponse = (req, res, handled, statusCodeOverride) => {
|
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
const pipeResponse = (res, readStream, errorTransformer) => {
|
|
104
|
-
readStream.on('data', res.write)
|
|
105
|
-
.on('end', res.end)
|
|
106
|
+
readStream.on('data', chunk => res.write(chunk))
|
|
107
|
+
.on('end', () => res.end())
|
|
106
108
|
.on('error', e => {
|
|
107
109
|
try {
|
|
108
110
|
readStream.destroy()
|
|
@@ -125,22 +127,18 @@ const doSerializeBody = (body, res) => {
|
|
|
125
127
|
return serialized?.data || ''
|
|
126
128
|
}
|
|
127
129
|
|
|
128
|
-
async function executeMiddleware (
|
|
129
|
-
if (!
|
|
130
|
+
async function executeMiddleware (processedMiddleware, req, res, errorTransformer, refId, e) {
|
|
131
|
+
if (!processedMiddleware) return
|
|
130
132
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
if (applicableMiddleware) applicableMiddleware = middleware.ALL.concat(applicableMiddleware)
|
|
134
|
-
else applicableMiddleware = middleware.ALL
|
|
135
|
-
}
|
|
133
|
+
const methodMiddleware = processedMiddleware[req.method]
|
|
134
|
+
const allMiddleware = processedMiddleware.ALL
|
|
136
135
|
|
|
137
|
-
if (!applicableMiddleware || applicableMiddleware.length === 0) {
|
|
138
|
-
return
|
|
139
|
-
}
|
|
140
136
|
if (e) {
|
|
141
|
-
await invokeMiddleware(
|
|
137
|
+
if (allMiddleware?.error) await invokeMiddleware(allMiddleware.error, req, res, e)
|
|
138
|
+
if (methodMiddleware?.error) await invokeMiddleware(methodMiddleware.error, req, res, e)
|
|
142
139
|
} else {
|
|
143
|
-
await invokeMiddleware(
|
|
140
|
+
if (allMiddleware?.normal) await invokeMiddleware(allMiddleware.normal, req, res)
|
|
141
|
+
if (methodMiddleware?.normal) await invokeMiddleware(methodMiddleware.normal, req, res)
|
|
144
142
|
}
|
|
145
143
|
}
|
|
146
144
|
|
|
@@ -148,39 +146,47 @@ const handleRequest = async (req, res, handler, middleware, errorTransformer) =>
|
|
|
148
146
|
try {
|
|
149
147
|
let reqBody
|
|
150
148
|
if (!handler.streamRequestBody) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
149
|
+
if (req.method === 'GET' || req.method === 'HEAD') {
|
|
150
|
+
reqBody = Promise.resolve(null)
|
|
151
|
+
} else {
|
|
152
|
+
let buffer
|
|
153
|
+
reqBody = new Promise(
|
|
154
|
+
resolve =>
|
|
155
|
+
res.onData((data, isLast) => {
|
|
156
|
+
const chunk = Buffer.concat([Buffer.from(data)])
|
|
157
|
+
buffer = buffer ? Buffer.concat([buffer, chunk]) : chunk
|
|
158
|
+
if (isLast) {
|
|
159
|
+
resolve(buffer)
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
)
|
|
163
|
+
}
|
|
162
164
|
} else {
|
|
163
165
|
const readable = new Readable({
|
|
164
166
|
read: () => {
|
|
165
167
|
}
|
|
166
168
|
})
|
|
167
|
-
res.onData(
|
|
168
|
-
if (data.byteLength
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
169
|
+
res.onData((data, isLast) => {
|
|
170
|
+
if (data.byteLength > 0 || isLast) {
|
|
171
|
+
readable.push(Buffer.concat([Buffer.from(data)]))
|
|
172
|
+
if (isLast) {
|
|
173
|
+
readable.push(null)
|
|
174
|
+
}
|
|
173
175
|
}
|
|
174
176
|
})
|
|
175
177
|
reqBody = Promise.resolve(readable)
|
|
176
178
|
}
|
|
177
|
-
|
|
179
|
+
if (middleware) {
|
|
180
|
+
await executeMiddleware(middleware, req, res, errorTransformer)
|
|
181
|
+
}
|
|
178
182
|
if (!res.writableEnded && !res.ended) {
|
|
179
183
|
await executeHandler(req.spliffyUrl, res, req, reqBody, handler, middleware, errorTransformer)
|
|
180
184
|
}
|
|
181
185
|
} catch (e) {
|
|
182
186
|
const refId = uuid()
|
|
183
|
-
|
|
187
|
+
if (middleware) {
|
|
188
|
+
await executeMiddleware(middleware, req, res, errorTransformer, refId, e)
|
|
189
|
+
}
|
|
184
190
|
if (!res.writableEnded) { endError(res, e, refId, errorTransformer) }
|
|
185
191
|
}
|
|
186
192
|
}
|
|
@@ -190,31 +196,41 @@ export const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS',
|
|
|
190
196
|
let currentDate = new Date().toISOString()
|
|
191
197
|
setInterval(() => { currentDate = new Date().toISOString() }, 1000)
|
|
192
198
|
|
|
193
|
-
export const createHandler = (handler, middleware, pathParameters, config) =>
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
res
|
|
199
|
+
export const createHandler = (handler, middleware, pathParameters, config) => {
|
|
200
|
+
const processedMiddleware = preProcessMiddleware(middleware)
|
|
201
|
+
return function (res, req) {
|
|
202
|
+
try {
|
|
203
|
+
res.cork(() => {
|
|
204
|
+
req = decorateRequest(req, pathParameters, res, config)
|
|
205
|
+
res = decorateResponse(res, req, finalizeResponse, config.errorTransformer, endError, config)
|
|
198
206
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
207
|
+
if (config.logAccess) {
|
|
208
|
+
// pre-fetch IPs for logging while still safe
|
|
209
|
+
req._ra = req.remoteAddress
|
|
210
|
+
req._pra = req.proxiedRemoteAddress
|
|
211
|
+
res.onEnd = writeAccess(req, res)
|
|
212
|
+
}
|
|
202
213
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
214
|
+
if (config.writeDateHeader) {
|
|
215
|
+
res.headers.date = currentDate
|
|
216
|
+
}
|
|
206
217
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
+
handleRequest(req, res, handler, processedMiddleware, config.errorTransformer)
|
|
219
|
+
.catch(e => {
|
|
220
|
+
log.error('Failed handling request', e)
|
|
221
|
+
if (!res.writableEnded) {
|
|
222
|
+
res.statusCode = 500
|
|
223
|
+
res.end()
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
} catch (e) {
|
|
228
|
+
log.error('Failed handling request', e)
|
|
229
|
+
if (!res.writableEnded) {
|
|
230
|
+
res.statusCode = 500
|
|
231
|
+
res.end()
|
|
232
|
+
}
|
|
233
|
+
}
|
|
218
234
|
}
|
|
219
235
|
}
|
|
220
236
|
|
|
@@ -223,32 +239,41 @@ export const createNotFoundHandler = config => {
|
|
|
223
239
|
const params = handler?.pathParameters || []
|
|
224
240
|
return (res, req) => {
|
|
225
241
|
try {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
242
|
+
res.cork(() => {
|
|
243
|
+
req = decorateRequest(req, params, res, config)
|
|
244
|
+
res = decorateResponse(res, req, finalizeResponse, config.errorTransformer, endError, config)
|
|
245
|
+
if (config.logAccess) {
|
|
246
|
+
req._ra = req.remoteAddress
|
|
247
|
+
req._pra = req.proxiedRemoteAddress
|
|
248
|
+
res.onEnd = writeAccess(req, res)
|
|
249
|
+
}
|
|
250
|
+
if (handler && typeof handler === 'object') {
|
|
251
|
+
const processedMiddleware = preProcessMiddleware(handler.middleware)
|
|
252
|
+
if (handler.handlers && typeof handler.handlers[req.method] === 'function') {
|
|
253
|
+
const h = handler.handlers[req.method]
|
|
254
|
+
if ('statusCodeOverride' in handler) {
|
|
255
|
+
h.statusCodeOverride = handler.statusCodeOverride
|
|
256
|
+
}
|
|
257
|
+
handleRequest(req, res,
|
|
258
|
+
h,
|
|
259
|
+
processedMiddleware,
|
|
260
|
+
config.errorTransformer
|
|
261
|
+
).catch((e) => {
|
|
262
|
+
log.error('Unexpected exception during request handling', e)
|
|
263
|
+
if (!res.writableEnded) {
|
|
264
|
+
res.statusCode = 500
|
|
265
|
+
res.end()
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
} else {
|
|
269
|
+
res.statusCode = 404
|
|
270
|
+
res.end()
|
|
235
271
|
}
|
|
236
|
-
handleRequest(req, res,
|
|
237
|
-
handler.handlers[req.method],
|
|
238
|
-
handler.middleware,
|
|
239
|
-
config.errorTransformer
|
|
240
|
-
).catch((e) => {
|
|
241
|
-
log.error('Unexpected exception during request handling', e)
|
|
242
|
-
res.statusCode = 500
|
|
243
|
-
})
|
|
244
272
|
} else {
|
|
245
273
|
res.statusCode = 404
|
|
246
274
|
res.end()
|
|
247
275
|
}
|
|
248
|
-
}
|
|
249
|
-
res.statusCode = 404
|
|
250
|
-
res.end()
|
|
251
|
-
}
|
|
276
|
+
})
|
|
252
277
|
} catch (e) {
|
|
253
278
|
log.error('Failed handling request', e)
|
|
254
279
|
}
|
package/src/middleware.mjs
CHANGED
|
@@ -61,26 +61,39 @@ export const validateMiddlewareArray = (arr) => {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
export function preProcessMiddleware (middleware) {
|
|
65
|
+
if (!middleware) return null
|
|
66
|
+
const processed = {}
|
|
67
|
+
let hasAny = false
|
|
68
|
+
for (const method in middleware) {
|
|
69
|
+
const list = middleware[method]
|
|
70
|
+
if (list && list.length > 0) {
|
|
71
|
+
processed[method] = {
|
|
72
|
+
normal: list.filter(mw => mw.length <= 3),
|
|
73
|
+
error: list.filter(mw => mw.length === 4)
|
|
74
|
+
}
|
|
75
|
+
hasAny = true
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return hasAny ? processed : null
|
|
79
|
+
}
|
|
80
|
+
|
|
64
81
|
export async function invokeMiddleware (middleware, req, res, reqErr) {
|
|
65
|
-
|
|
66
|
-
|
|
82
|
+
if (!middleware || middleware.length === 0) return
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
let current = 0
|
|
67
85
|
const next = (err) => {
|
|
68
|
-
if (err) reject(err)
|
|
69
|
-
if (res.writableEnded) {
|
|
70
|
-
resolve()
|
|
71
|
-
return
|
|
86
|
+
if (err) return reject(err)
|
|
87
|
+
if (res.writableEnded || current === middleware.length) {
|
|
88
|
+
return resolve()
|
|
72
89
|
}
|
|
73
|
-
current++
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
} catch (e) {
|
|
81
|
-
log.error('Middleware threw exception', e)
|
|
82
|
-
reject(e)
|
|
83
|
-
}
|
|
90
|
+
const mw = middleware[current++]
|
|
91
|
+
try {
|
|
92
|
+
if (reqErr) mw(reqErr, req, res, next)
|
|
93
|
+
else mw(req, res, next)
|
|
94
|
+
} catch (e) {
|
|
95
|
+
log.error('Middleware threw exception', e)
|
|
96
|
+
reject(e)
|
|
84
97
|
}
|
|
85
98
|
}
|
|
86
99
|
|