@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 CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@srfnstack/spliffy",
3
- "version": "1.2.8",
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": ">=16 <=20"
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 => String.fromCharCode.apply(null, new Int8Array(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
- export const setCookie = (res) => function () {
27
- return res.setHeader('Set-Cookie', [...(res.getHeader('Set-Cookie') || []), cookie.serialize(...arguments)])
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
- 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]
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 toArrayBuffer (buffer) {
71
- return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)
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, { acceptsDefault }) {
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[normalizeHeader(header)] = value
91
+ res.headers[header.toLowerCase()] = value
86
92
  }
87
93
  res.removeHeader = header => {
88
- delete res.headers[normalizeHeader(header)]
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 of Object.keys(res.headers)) {
100
- if (Array.isArray(res.headers[header])) {
101
- for (const multiple of res.headers[header]) {
102
- res.writeHeader(header, multiple.toString())
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, res.headers[header].toString())
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 of Object.keys(headers)) {
115
- res.headers[normalizeHeader(header)] = headers[header]
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[normalizeHeader(header)]
126
+ return res.headers[header.toLowerCase()]
120
127
  }
121
128
  res.status = (code) => {
122
129
  res.statusCode = code
123
- return this
130
+ return res
124
131
  }
125
132
 
126
- res.uwsWrite = res.write
133
+ const uwsWrite = res.write
127
134
  res.write = (chunk, encoding, cb) => {
128
135
  res.cork(() => {
129
- try {
130
- res.streaming = true
131
- res.flushHeaders()
132
- let data
133
- if (chunk instanceof Buffer) {
134
- data = toArrayBuffer(chunk)
135
- } else if (typeof chunk === 'string') {
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
- let outStream
149
+
155
150
  res.getWritable = () => {
156
- if (!outStream) {
151
+ if (!res.outStream) {
157
152
  res.streaming = true
158
- outStream = new Writable({
159
- write: res.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
- return
179
- }
180
- // provide writableEnded like node does, with slightly different behavior
181
- if (!res.writableEnded) {
182
- res.cork(() => {
183
- res.flushHeaders()
184
- uwsEnd.call(res, body)
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 = function (code, location) {
195
- if (arguments.length === 1) {
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 = setCookie(res)
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
- await executeMiddleware(middleware, req, res, errorTransformer, refId, e)
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 = new Date().getTime()
78
+ const start = Date.now()
77
79
  return () => {
78
- log.access(compressIpv6(req.remoteAddress), compressIpv6(res.proxiedRemoteAddress) || '', res.statusCode, req.method, req.url, new Date().getTime() - start + 'ms')
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 (middleware, req, res, errorTransformer, refId, e) {
129
- if (!middleware) return
130
+ async function executeMiddleware (processedMiddleware, req, res, errorTransformer, refId, e) {
131
+ if (!processedMiddleware) return
130
132
 
131
- let applicableMiddleware = middleware[req.method]
132
- if (middleware.ALL) {
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(applicableMiddleware.filter(mw => mw.length === 4), req, res, e)
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(applicableMiddleware.filter(mw => mw.length === 3), req, res)
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
- let buffer
152
- reqBody = 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
- )
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(async (data, isLast) => {
168
- if (data.byteLength === 0 && !isLast) return
169
- // data must be copied so it isn't lost
170
- readable.push(Buffer.concat([Buffer.from(data)]))
171
- if (isLast) {
172
- readable.push(null)
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
- await executeMiddleware(middleware, req, res, errorTransformer)
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
- await executeMiddleware(middleware, req, res, errorTransformer, refId, e)
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) => function (res, req) {
194
- try {
195
- res.cork(() => {
196
- req = decorateRequest(req, pathParameters, res, config)
197
- res = decorateResponse(res, req, finalizeResponse, config.errorTransformer, endError, config)
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
- if (config.logAccess) {
200
- res.onEnd = writeAccess(req, res)
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
- if (config.writeDateHeader) {
204
- res.headers.date = currentDate
205
- }
214
+ if (config.writeDateHeader) {
215
+ res.headers.date = currentDate
216
+ }
206
217
 
207
- handleRequest(req, res, handler, middleware, config.errorTransformer)
208
- .catch(e => {
209
- log.error('Failed handling request', e)
210
- res.statusCode = 500
211
- res.end()
212
- })
213
- })
214
- } catch (e) {
215
- log.error('Failed handling request', e)
216
- res.statusCode = 500
217
- res.end()
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
- req = decorateRequest(req, params, res, config)
227
- res = decorateResponse(res, req, finalizeResponse, config.errorTransformer, endError, config)
228
- if (config.logAccess) {
229
- res.onEnd = writeAccess(req, res)
230
- }
231
- if (handler && typeof handler === 'object') {
232
- if (handler.handlers && typeof handler.handlers[req.method] === 'function') {
233
- if ('statusCodeOverride' in handler) {
234
- handler.handlers[req.method].statusCodeOverride = handler.statusCodeOverride
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
- } else {
249
- res.statusCode = 404
250
- res.end()
251
- }
276
+ })
252
277
  } catch (e) {
253
278
  log.error('Failed handling request', e)
254
279
  }
@@ -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
- await new Promise((resolve, reject) => {
66
- let current = -1
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
- if (current === middleware.length) {
75
- resolve()
76
- } else {
77
- try {
78
- if (reqErr) middleware[current](reqErr, req, res, next)
79
- else middleware[current](req, res, next)
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