@srfnstack/spliffy 1.3.0 → 1.4.1
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 +1 -1
- package/src/decorator.mjs +171 -164
- package/src/handler.mjs +126 -159
- package/src/index.mjs +2 -0
- package/src/routes.mjs +8 -0
- package/src/server.mjs +1 -1
package/package.json
CHANGED
package/src/decorator.mjs
CHANGED
|
@@ -1,207 +1,214 @@
|
|
|
1
1
|
import cookie from 'cookie'
|
|
2
|
-
import http from 'http'
|
|
3
2
|
import { parseQuery } from './url.mjs'
|
|
4
|
-
import log from './log.mjs'
|
|
5
3
|
import { v4 as uuid } from 'uuid'
|
|
6
4
|
import stream from 'stream'
|
|
7
|
-
import
|
|
5
|
+
import { defaultStatusMessages } from './httpStatusCodes.mjs'
|
|
8
6
|
|
|
9
7
|
const { Writable } = stream
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
_read: true,
|
|
16
|
-
destroy: true,
|
|
17
|
-
_addHeaderLines: true,
|
|
18
|
-
_addHeaderLine: true,
|
|
19
|
-
_dump: true,
|
|
20
|
-
__proto__: true
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const reqProtoProps = () => Object.keys(http.IncomingMessage.prototype).filter(p => !excludedMessageProps[p])
|
|
9
|
+
export class SpliffyRequest {
|
|
10
|
+
constructor (uwsReq, paramToIndex, res, config) {
|
|
11
|
+
this.init(uwsReq, paramToIndex, res, config)
|
|
12
|
+
}
|
|
24
13
|
|
|
25
|
-
|
|
26
|
-
constructor (uwsReq, pathParameters, res, config) {
|
|
27
|
-
const query = uwsReq.getQuery()
|
|
14
|
+
init (uwsReq, paramToIndex, res, config, method, urlPath) {
|
|
28
15
|
this.res = res
|
|
29
|
-
this.
|
|
30
|
-
this.
|
|
31
|
-
this.
|
|
32
|
-
this.
|
|
33
|
-
uwsReq.
|
|
34
|
-
|
|
35
|
-
|
|
16
|
+
this._uwsReq = uwsReq
|
|
17
|
+
this._config = config
|
|
18
|
+
this._paramToIndex = paramToIndex
|
|
19
|
+
this.path = urlPath || (uwsReq ? uwsReq.getUrl() : '')
|
|
20
|
+
this.method = method || (uwsReq ? uwsReq.getMethod().toUpperCase() : '')
|
|
21
|
+
this._url = null
|
|
22
|
+
this._headers = null
|
|
23
|
+
this._spliffyUrl = null
|
|
24
|
+
this._cookies = null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
forceCache () {
|
|
28
|
+
if (this._headers === null) this._cacheHeaders()
|
|
29
|
+
if (this._spliffyUrl === null) this._cacheSpliffyUrl()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_cacheHeaders () {
|
|
33
|
+
if (this._headers === null) {
|
|
34
|
+
this._headers = {}
|
|
35
|
+
this._uwsReq.forEach((header, value) => { this._headers[header] = value })
|
|
36
|
+
}
|
|
37
|
+
}
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
_cacheSpliffyUrl () {
|
|
40
|
+
if (this._spliffyUrl === null) {
|
|
41
|
+
const q = this._uwsReq.getQuery()
|
|
42
|
+
const p2i = this._paramToIndex
|
|
43
|
+
const params = {}
|
|
44
|
+
for (const name in p2i) {
|
|
45
|
+
params[name] = this._uwsReq.getParameter(p2i[name])
|
|
46
|
+
}
|
|
47
|
+
this._spliffyUrl = {
|
|
48
|
+
path: this.path,
|
|
49
|
+
query: (q && parseQuery(q, this._config.decodeQueryParameters)) || {},
|
|
50
|
+
param: name => params[name]
|
|
40
51
|
}
|
|
41
52
|
}
|
|
53
|
+
}
|
|
42
54
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
this.spliffyUrl = {
|
|
49
|
-
path: this.path,
|
|
50
|
-
query: (query && parseQuery(query, config.decodeQueryParameters)) || {},
|
|
51
|
-
param: name => uwsReq.getParameter(paramToIndex[name])
|
|
55
|
+
get url () {
|
|
56
|
+
if (this._url === null) {
|
|
57
|
+
const q = this._uwsReq.getQuery()
|
|
58
|
+
this._url = q ? `${this.path}?${q}` : this.path
|
|
52
59
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
return this._url
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get headers () {
|
|
64
|
+
if (this._headers === null) this._cacheHeaders()
|
|
65
|
+
return this._headers
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get spliffyUrl () {
|
|
69
|
+
if (this._spliffyUrl === null) this._cacheSpliffyUrl()
|
|
70
|
+
return this._spliffyUrl
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
get query () { return this.spliffyUrl.query }
|
|
74
|
+
get cookies () {
|
|
75
|
+
if (this._cookies === null) {
|
|
76
|
+
this._cookies = (this.headers.cookie && cookie.parse(this.headers.cookie)) || {}
|
|
56
77
|
}
|
|
78
|
+
return this._cookies
|
|
57
79
|
}
|
|
58
80
|
|
|
59
81
|
get remoteAddress () {
|
|
60
|
-
const val =
|
|
82
|
+
const val = Buffer.from(this.res.getRemoteAddressAsText()).toString()
|
|
61
83
|
Object.defineProperty(this, 'remoteAddress', { value: val, enumerable: true })
|
|
62
84
|
return val
|
|
63
85
|
}
|
|
64
86
|
|
|
65
87
|
get proxiedRemoteAddress () {
|
|
66
|
-
const val =
|
|
88
|
+
const val = Buffer.from(this.res.getProxiedRemoteAddressAsText()).toString()
|
|
67
89
|
Object.defineProperty(this, 'proxiedRemoteAddress', { value: val, enumerable: true })
|
|
68
90
|
return val
|
|
69
91
|
}
|
|
70
92
|
|
|
71
|
-
get (header) {
|
|
72
|
-
return this.headers[header.toLowerCase()]
|
|
73
|
-
}
|
|
93
|
+
get (header) { return this.headers[header.toLowerCase()] }
|
|
74
94
|
}
|
|
75
95
|
|
|
76
|
-
export function decorateRequest (uwsReq,
|
|
77
|
-
return new SpliffyRequest(uwsReq,
|
|
96
|
+
export function decorateRequest (uwsReq, paramToIndex, res, config) {
|
|
97
|
+
return new SpliffyRequest(uwsReq, paramToIndex, res, config)
|
|
78
98
|
}
|
|
79
99
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
100
|
+
const resProto = {
|
|
101
|
+
setHeader (h, v) { this.headers[h.toLowerCase()] = v },
|
|
102
|
+
removeHeader (h) { delete this.headers[h.toLowerCase()] },
|
|
103
|
+
assignHeaders (headers) {
|
|
104
|
+
for (const h in headers) { this.headers[h.toLowerCase()] = headers[h] }
|
|
105
|
+
},
|
|
106
|
+
getHeader (h) { return this.headers[h.toLowerCase()] },
|
|
107
|
+
status (c) { this.statusCode = c; return this },
|
|
108
|
+
writeHead (s, h) { this.statusCode = s; if (h) this.assignHeaders(h) },
|
|
109
|
+
redirect (c, l) {
|
|
110
|
+
if (typeof c === 'string') { l = c; c = 301 }
|
|
111
|
+
return this._finalizeResponse(this._sReq, this, { statusCode: c, headers: { location: l } })
|
|
112
|
+
},
|
|
113
|
+
send (b) { return this._finalizeResponse(this._sReq, this, b) },
|
|
114
|
+
json (b) { return this._finalizeResponse(this._sReq, this, b) },
|
|
115
|
+
setCookie (n, v, o) {
|
|
116
|
+
const s = cookie.serialize(n, v, o)
|
|
117
|
+
const e = this.headers['set-cookie']
|
|
118
|
+
if (e) {
|
|
119
|
+
if (Array.isArray(e)) e.push(s)
|
|
120
|
+
else this.headers['set-cookie'] = [e, s]
|
|
121
|
+
} else this.headers['set-cookie'] = s
|
|
122
|
+
},
|
|
123
|
+
cookie (n, v, o) { return this.setCookie(n, v, o) },
|
|
124
|
+
flushHeaders (noCork) {
|
|
125
|
+
if (this.headersSent) return
|
|
126
|
+
this.headersSent = true
|
|
127
|
+
const flush = () => {
|
|
128
|
+
if (this.statusCode && this.statusCode !== 200) {
|
|
129
|
+
this.writeStatus(this.statusCode + ' ' + (defaultStatusMessages[this.statusCode] || ''))
|
|
130
|
+
}
|
|
131
|
+
const headers = this.headers
|
|
132
|
+
for (const h in headers) {
|
|
133
|
+
const v = headers[h]
|
|
134
|
+
if (Array.isArray(v)) {
|
|
135
|
+
for (let i = 0, l = v.length; i < l; i++) this.writeHeader(h, String(v[i]))
|
|
136
|
+
} else {
|
|
137
|
+
this.writeHeader(h, String(v))
|
|
110
138
|
}
|
|
111
|
-
} else {
|
|
112
|
-
res.writeHeader(header, val.toString())
|
|
113
139
|
}
|
|
114
140
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return res
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const uwsWrite = res.write
|
|
134
|
-
res.write = (chunk, encoding, cb) => {
|
|
135
|
-
res.cork(() => {
|
|
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)
|
|
143
|
-
}
|
|
144
|
-
const result = uwsWrite.call(res, data)
|
|
145
|
-
if (typeof cb === 'function') cb()
|
|
141
|
+
if (noCork) flush()
|
|
142
|
+
else this.cork(flush)
|
|
143
|
+
},
|
|
144
|
+
ensureOnAborted () {
|
|
145
|
+
if (this._onAbortedRegistered) return
|
|
146
|
+
this._onAbortedRegistered = true
|
|
147
|
+
this.onAborted(() => { this.ended = true; this.writableEnded = true; this.finalized = true })
|
|
148
|
+
},
|
|
149
|
+
write (chunk, encoding, cb) {
|
|
150
|
+
this.ensureOnAborted()
|
|
151
|
+
this.streaming = true
|
|
152
|
+
return this.cork(() => {
|
|
153
|
+
if (!this.headersSent) this.flushHeaders(true)
|
|
154
|
+
const result = this._uwsWrite((chunk instanceof Buffer || chunk instanceof Uint8Array || typeof chunk === 'string') ? chunk : JSON.stringify(chunk))
|
|
155
|
+
if (cb) cb()
|
|
146
156
|
return result
|
|
147
157
|
})
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
})
|
|
158
|
+
},
|
|
159
|
+
end (body) {
|
|
160
|
+
if (this.ended) return
|
|
161
|
+
this.ended = true
|
|
162
|
+
this.writableEnded = true
|
|
163
|
+
this.cork(() => {
|
|
164
|
+
if (!this.headersSent) this.flushHeaders(true)
|
|
165
|
+
this._uwsEnd(body || '')
|
|
166
|
+
})
|
|
167
|
+
if (this.onEnd) this.onEnd()
|
|
168
|
+
},
|
|
169
|
+
getWritable () {
|
|
170
|
+
this.ensureOnAborted()
|
|
171
|
+
if (!this.outStream) {
|
|
172
|
+
this.streaming = true
|
|
173
|
+
this.outStream = new Writable({ write: (c, e, callback) => { this.write(c, e, callback) } })
|
|
174
|
+
.on('finish', () => this.end())
|
|
175
|
+
.on('error', e => { try { this.outStream.destroy() } finally { this._endError(this, e, uuid(), this._errorTransformer) } })
|
|
166
176
|
}
|
|
167
|
-
return
|
|
177
|
+
return this.outStream
|
|
168
178
|
}
|
|
179
|
+
}
|
|
169
180
|
|
|
170
|
-
|
|
181
|
+
export function decorateResponse (res, sReq, finalizeResponse, errorTransformer, endError, config) {
|
|
182
|
+
res._sReq = sReq
|
|
183
|
+
res._finalizeResponse = finalizeResponse
|
|
184
|
+
res._errorTransformer = errorTransformer
|
|
185
|
+
res._endError = endError
|
|
186
|
+
res._onAbortedRegistered = false
|
|
187
|
+
res.ensureOnAborted = resProto.ensureOnAborted
|
|
188
|
+
res.acceptsDefault = config.acceptsDefault
|
|
189
|
+
res.headers = {}
|
|
190
|
+
res.headersSent = false
|
|
191
|
+
res.setHeader = resProto.setHeader
|
|
192
|
+
res.removeHeader = resProto.removeHeader
|
|
193
|
+
res.assignHeaders = resProto.assignHeaders
|
|
194
|
+
res.getHeader = resProto.getHeader
|
|
195
|
+
res.status = resProto.status
|
|
196
|
+
res.writeHead = resProto.writeHead
|
|
197
|
+
res.flushHeaders = resProto.flushHeaders
|
|
198
|
+
res.redirect = resProto.redirect
|
|
199
|
+
res.send = resProto.send
|
|
200
|
+
res.json = resProto.json
|
|
201
|
+
res.setCookie = resProto.setCookie
|
|
202
|
+
res.cookie = resProto.cookie
|
|
203
|
+
|
|
204
|
+
res._uwsWrite = res.write
|
|
205
|
+
res.write = resProto.write
|
|
206
|
+
|
|
207
|
+
res.getWritable = resProto.getWritable
|
|
208
|
+
|
|
209
|
+
res._uwsEnd = res.end
|
|
171
210
|
res.ended = false
|
|
172
|
-
res.end =
|
|
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
|
-
})
|
|
181
|
-
}
|
|
211
|
+
res.end = resProto.end
|
|
182
212
|
|
|
183
|
-
res.redirect = (code, location) => {
|
|
184
|
-
if (typeof code === 'string') {
|
|
185
|
-
location = code
|
|
186
|
-
code = httpStatusCodes.MOVED_PERMANENTLY
|
|
187
|
-
}
|
|
188
|
-
return finalizeResponse(req, res, {
|
|
189
|
-
statusCode: code,
|
|
190
|
-
headers: { location }
|
|
191
|
-
})
|
|
192
|
-
}
|
|
193
|
-
res.send = (body) => finalizeResponse(req, res, body)
|
|
194
|
-
res.json = res.send
|
|
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
|
-
}
|
|
205
|
-
res.cookie = res.setCookie
|
|
206
213
|
return res
|
|
207
214
|
}
|
package/src/handler.mjs
CHANGED
|
@@ -1,41 +1,28 @@
|
|
|
1
1
|
import log from './log.mjs'
|
|
2
2
|
import { deserializeBody, serializeBody } from './content.mjs'
|
|
3
3
|
import { invokeMiddleware, preProcessMiddleware } from './middleware.mjs'
|
|
4
|
-
import { decorateResponse,
|
|
4
|
+
import { decorateResponse, SpliffyRequest } from './decorator.mjs'
|
|
5
5
|
import { v4 as uuid } from 'uuid'
|
|
6
6
|
import stream from 'stream'
|
|
7
7
|
const { Readable } = stream
|
|
8
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
|
-
}
|
|
9
|
+
const NULL_PROMISE = Promise.resolve(null)
|
|
31
10
|
|
|
11
|
+
const executeHandler = async (url, res, req, bodyPromise, handler, middleware, errorTransformer) => {
|
|
32
12
|
try {
|
|
33
|
-
const
|
|
34
|
-
|
|
13
|
+
const bodyContent = await (bodyPromise || NULL_PROMISE)
|
|
14
|
+
if (!res.writableEnded) {
|
|
15
|
+
const deserializedBody = (bodyContent instanceof Readable || !bodyContent)
|
|
16
|
+
? bodyContent
|
|
17
|
+
: deserializeBody(bodyContent, req.headers['content-type'], res.acceptsDefault)
|
|
18
|
+
|
|
19
|
+
const handled = await handler({ url, bodyPromise: Promise.resolve(deserializedBody), headers: req.headers, req, res })
|
|
20
|
+
finalizeResponse(req, res, handled, handler.statusCodeOverride)
|
|
21
|
+
}
|
|
35
22
|
} catch (e) {
|
|
36
23
|
const refId = uuid()
|
|
37
24
|
if (middleware) {
|
|
38
|
-
await executeMiddleware(middleware, req, res, errorTransformer, refId, e)
|
|
25
|
+
try { await executeMiddleware(middleware, req, res, errorTransformer, refId, e) } catch (me) { log.error(me) }
|
|
39
26
|
}
|
|
40
27
|
endError(res, e, refId, errorTransformer)
|
|
41
28
|
}
|
|
@@ -50,55 +37,37 @@ const endError = (res, e, refId, errorTransformer) => {
|
|
|
50
37
|
}
|
|
51
38
|
res.headers['x-ref-id'] = refId
|
|
52
39
|
const status = e.statusCode || 500
|
|
53
|
-
if (status === 500)
|
|
54
|
-
log.error(e)
|
|
55
|
-
}
|
|
40
|
+
if (status === 500) log.error(e)
|
|
56
41
|
end(res, status, null, e.body || '')
|
|
57
42
|
}
|
|
58
43
|
|
|
59
44
|
const end = (res, defaultStatusCode, statusCodeOverride, body) => {
|
|
60
|
-
// status set directly on res wins
|
|
61
45
|
res.statusCode = statusCodeOverride || res.statusCode || defaultStatusCode
|
|
46
|
+
if (currentDate) res.setHeader('Date', currentDate)
|
|
62
47
|
if (body instanceof Readable || res.streaming) {
|
|
63
48
|
res.streaming = true
|
|
64
|
-
if (body instanceof Readable)
|
|
65
|
-
pipeResponse(res, body)
|
|
66
|
-
}
|
|
67
|
-
// handler is responsible for ending the response if they are streaming
|
|
49
|
+
if (body instanceof Readable) pipeResponse(res, body)
|
|
68
50
|
} else {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const compressIpv6 = ip => ip && ip.includes(':') ? ip.replaceAll(ipv6CompressRegex, '::') : ip
|
|
76
|
-
|
|
77
|
-
const writeAccess = function (req, res) {
|
|
78
|
-
const start = Date.now()
|
|
79
|
-
return () => {
|
|
80
|
-
log.access(compressIpv6(req.remoteAddress), compressIpv6(res.proxiedRemoteAddress) || '', res.statusCode, req.method, req.url, Date.now() - start + 'ms')
|
|
51
|
+
if (typeof body === 'string' || !body || body instanceof Buffer) {
|
|
52
|
+
res.end(body || '')
|
|
53
|
+
} else {
|
|
54
|
+
res.end(doSerializeBody(body, res) || '')
|
|
55
|
+
}
|
|
81
56
|
}
|
|
82
57
|
}
|
|
83
58
|
|
|
84
59
|
const finalizeResponse = (req, res, handled, statusCodeOverride) => {
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
end(res, handled.statusCode || 200, statusCodeOverride, handled.body)
|
|
97
|
-
} else {
|
|
98
|
-
end(res, 200, statusCodeOverride, handled)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
res.finalized = true
|
|
60
|
+
if (res.finalized) return
|
|
61
|
+
res.finalized = true
|
|
62
|
+
|
|
63
|
+
if (typeof handled === 'string' || !handled || handled instanceof Buffer) {
|
|
64
|
+
end(res, 200, statusCodeOverride, handled)
|
|
65
|
+
} else if (handled.body || handled.statusMessage || handled.statusCode || handled.headers) {
|
|
66
|
+
if (handled.headers) res.assignHeaders(handled.headers)
|
|
67
|
+
if (handled.statusMessage) res.statusMessage = handled.statusMessage
|
|
68
|
+
end(res, handled.statusCode || 200, statusCodeOverride, handled.body)
|
|
69
|
+
} else {
|
|
70
|
+
end(res, 200, statusCodeOverride, handled)
|
|
102
71
|
}
|
|
103
72
|
}
|
|
104
73
|
|
|
@@ -106,21 +75,13 @@ const pipeResponse = (res, readStream, errorTransformer) => {
|
|
|
106
75
|
readStream.on('data', chunk => res.write(chunk))
|
|
107
76
|
.on('end', () => res.end())
|
|
108
77
|
.on('error', e => {
|
|
109
|
-
try {
|
|
110
|
-
readStream.destroy()
|
|
111
|
-
} finally {
|
|
112
|
-
endError(res, e, uuid(), errorTransformer)
|
|
113
|
-
}
|
|
78
|
+
try { readStream.destroy() } finally { endError(res, e, uuid(), errorTransformer) }
|
|
114
79
|
})
|
|
115
80
|
}
|
|
116
81
|
|
|
117
82
|
const doSerializeBody = (body, res) => {
|
|
118
|
-
|
|
119
|
-
return body
|
|
120
|
-
}
|
|
121
|
-
const contentType = res.getHeader('content-type')
|
|
83
|
+
const contentType = res.headers['content-type']
|
|
122
84
|
const serialized = serializeBody(body, contentType, res.acceptsDefault)
|
|
123
|
-
|
|
124
85
|
if (serialized?.contentType && !contentType) {
|
|
125
86
|
res.headers['content-type'] = serialized.contentType
|
|
126
87
|
}
|
|
@@ -129,10 +90,8 @@ const doSerializeBody = (body, res) => {
|
|
|
129
90
|
|
|
130
91
|
async function executeMiddleware (processedMiddleware, req, res, errorTransformer, refId, e) {
|
|
131
92
|
if (!processedMiddleware) return
|
|
132
|
-
|
|
133
93
|
const methodMiddleware = processedMiddleware[req.method]
|
|
134
94
|
const allMiddleware = processedMiddleware.ALL
|
|
135
|
-
|
|
136
95
|
if (e) {
|
|
137
96
|
if (allMiddleware?.error) await invokeMiddleware(allMiddleware.error, req, res, e)
|
|
138
97
|
if (methodMiddleware?.error) await invokeMiddleware(methodMiddleware.error, req, res, e)
|
|
@@ -142,80 +101,96 @@ async function executeMiddleware (processedMiddleware, req, res, errorTransforme
|
|
|
142
101
|
}
|
|
143
102
|
}
|
|
144
103
|
|
|
145
|
-
const
|
|
104
|
+
export const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD', 'CONNECT', 'TRACE', 'WEBSOCKET']
|
|
105
|
+
|
|
106
|
+
let currentDate = ''
|
|
107
|
+
let dateInterval = null
|
|
108
|
+
|
|
109
|
+
const handleRequest = async (sReq, res, handler, processedMiddleware, config) => {
|
|
146
110
|
try {
|
|
147
|
-
let
|
|
148
|
-
if (
|
|
149
|
-
if (
|
|
150
|
-
|
|
111
|
+
let reqBodyPromise = NULL_PROMISE
|
|
112
|
+
if (sReq.method !== 'GET' && sReq.method !== 'HEAD') {
|
|
113
|
+
if (handler.streamRequestBody) {
|
|
114
|
+
const readable = new Readable({ read () {} })
|
|
115
|
+
reqBodyPromise = Promise.resolve(readable)
|
|
116
|
+
res.onData((data, isLast) => {
|
|
117
|
+
if (data.byteLength > 0 || isLast) {
|
|
118
|
+
readable.push(Buffer.concat([Buffer.from(data)]))
|
|
119
|
+
if (isLast) readable.push(null)
|
|
120
|
+
}
|
|
121
|
+
})
|
|
151
122
|
} else {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
buffer = buffer ? Buffer.concat([buffer, chunk]) : chunk
|
|
158
|
-
if (isLast) {
|
|
159
|
-
resolve(buffer)
|
|
160
|
-
}
|
|
161
|
-
})
|
|
162
|
-
)
|
|
123
|
+
reqBodyPromise = new Promise(resolve => res.onData((data, isLast) => {
|
|
124
|
+
const chunk = Buffer.concat([Buffer.from(data)])
|
|
125
|
+
res._buffer = res._buffer ? Buffer.concat([res._buffer, chunk]) : chunk
|
|
126
|
+
if (isLast) resolve(res._buffer)
|
|
127
|
+
}))
|
|
163
128
|
}
|
|
164
|
-
} else {
|
|
165
|
-
const readable = new Readable({
|
|
166
|
-
read: () => {
|
|
167
|
-
}
|
|
168
|
-
})
|
|
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
|
-
}
|
|
175
|
-
}
|
|
176
|
-
})
|
|
177
|
-
reqBody = Promise.resolve(readable)
|
|
178
|
-
}
|
|
179
|
-
if (middleware) {
|
|
180
|
-
await executeMiddleware(middleware, req, res, errorTransformer)
|
|
181
129
|
}
|
|
182
|
-
|
|
183
|
-
|
|
130
|
+
|
|
131
|
+
if (processedMiddleware) {
|
|
132
|
+
await executeMiddleware(processedMiddleware, sReq, res, config.errorTransformer)
|
|
133
|
+
if (!res.writableEnded && !res.ended) {
|
|
134
|
+
await executeHandler(sReq.spliffyUrl, res, sReq, reqBodyPromise, handler, processedMiddleware, config.errorTransformer)
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
await executeHandler(sReq.spliffyUrl, res, sReq, reqBodyPromise, handler, null, config.errorTransformer)
|
|
184
138
|
}
|
|
185
139
|
} catch (e) {
|
|
186
140
|
const refId = uuid()
|
|
187
|
-
if (
|
|
188
|
-
await executeMiddleware(
|
|
141
|
+
if (processedMiddleware) {
|
|
142
|
+
await executeMiddleware(processedMiddleware, sReq, res, config.errorTransformer, refId, e)
|
|
189
143
|
}
|
|
190
|
-
if (!res.writableEnded) { endError(res, e, refId, errorTransformer) }
|
|
144
|
+
if (!res.writableEnded) { endError(res, e, refId, config.errorTransformer) }
|
|
191
145
|
}
|
|
192
146
|
}
|
|
193
147
|
|
|
194
|
-
export const
|
|
148
|
+
export const createHandler = (handler, middleware, paramToIndex, config, urlPath) => {
|
|
149
|
+
const processedMiddleware = preProcessMiddleware(middleware)
|
|
150
|
+
if (config.writeDateHeader && !dateInterval) {
|
|
151
|
+
currentDate = new Date().toUTCString()
|
|
152
|
+
dateInterval = setInterval(() => { currentDate = new Date().toUTCString() }, 1000)
|
|
153
|
+
}
|
|
195
154
|
|
|
196
|
-
|
|
197
|
-
|
|
155
|
+
const isWildcardPath = urlPath?.indexOf('*') > -1
|
|
156
|
+
|
|
157
|
+
// Pre-allocated request object for sync path
|
|
158
|
+
const syncSReq = new SpliffyRequest(null, paramToIndex, null, config)
|
|
159
|
+
const syncContext = {
|
|
160
|
+
get url () { return syncSReq.spliffyUrl },
|
|
161
|
+
bodyPromise: NULL_PROMISE,
|
|
162
|
+
get headers () { return syncSReq.headers },
|
|
163
|
+
req: syncSReq,
|
|
164
|
+
res: null
|
|
165
|
+
}
|
|
198
166
|
|
|
199
|
-
export const createHandler = (handler, middleware, pathParameters, config) => {
|
|
200
|
-
const processedMiddleware = preProcessMiddleware(middleware)
|
|
201
167
|
return function (res, req) {
|
|
202
168
|
try {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
169
|
+
const rawMethod = req.getMethod()
|
|
170
|
+
if (!processedMiddleware && !handler.streamRequestBody && (rawMethod === 'get' || rawMethod === 'head')) {
|
|
171
|
+
// HYPER FAST PATH
|
|
172
|
+
const method = rawMethod === 'get' ? 'GET' : 'HEAD'
|
|
173
|
+
syncSReq.init(req, paramToIndex, res, config, method, isWildcardPath ? null : urlPath)
|
|
174
|
+
syncContext.res = res
|
|
175
|
+
decorateResponse(res, syncSReq, finalizeResponse, config.errorTransformer, endError, config)
|
|
176
|
+
const handled = handler(syncContext)
|
|
177
|
+
if (handled instanceof Promise) {
|
|
178
|
+
syncSReq.forceCache()
|
|
179
|
+
res.ensureOnAborted()
|
|
180
|
+
handled.then(h => finalizeResponse(syncSReq, res, h, handler.statusCodeOverride))
|
|
181
|
+
.catch(e => endError(res, e, uuid(), config.errorTransformer))
|
|
182
|
+
} else if (typeof handled === 'string' || handled instanceof Buffer) {
|
|
183
|
+
end(res, 200, handler.statusCodeOverride, handled)
|
|
184
|
+
} else {
|
|
185
|
+
finalizeResponse(syncSReq, res, handled, handler.statusCodeOverride)
|
|
216
186
|
}
|
|
187
|
+
} else {
|
|
188
|
+
const sReq = new SpliffyRequest(req, paramToIndex, res, config)
|
|
189
|
+
sReq.forceCache()
|
|
190
|
+
decorateResponse(res, sReq, finalizeResponse, config.errorTransformer, endError, config)
|
|
191
|
+
res.ensureOnAborted()
|
|
217
192
|
|
|
218
|
-
handleRequest(
|
|
193
|
+
handleRequest(sReq, res, handler, processedMiddleware, config)
|
|
219
194
|
.catch(e => {
|
|
220
195
|
log.error('Failed handling request', e)
|
|
221
196
|
if (!res.writableEnded) {
|
|
@@ -223,7 +198,7 @@ export const createHandler = (handler, middleware, pathParameters, config) => {
|
|
|
223
198
|
res.end()
|
|
224
199
|
}
|
|
225
200
|
})
|
|
226
|
-
}
|
|
201
|
+
}
|
|
227
202
|
} catch (e) {
|
|
228
203
|
log.error('Failed handling request', e)
|
|
229
204
|
if (!res.writableEnded) {
|
|
@@ -236,44 +211,36 @@ export const createHandler = (handler, middleware, pathParameters, config) => {
|
|
|
236
211
|
|
|
237
212
|
export const createNotFoundHandler = config => {
|
|
238
213
|
const handler = config.defaultRouteHandler || config.notFoundRouteHandler
|
|
239
|
-
const
|
|
214
|
+
const paramToIndex = handler?.paramToIndex || {}
|
|
240
215
|
return (res, req) => {
|
|
241
216
|
try {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
h.statusCodeOverride = handler.statusCodeOverride
|
|
256
|
-
}
|
|
257
|
-
handleRequest(req, res,
|
|
258
|
-
h,
|
|
259
|
-
processedMiddleware,
|
|
260
|
-
config.errorTransformer
|
|
261
|
-
).catch((e) => {
|
|
217
|
+
const sReq = new SpliffyRequest(req, paramToIndex, res, config)
|
|
218
|
+
sReq.forceCache()
|
|
219
|
+
decorateResponse(res, sReq, finalizeResponse, config.errorTransformer, endError, config)
|
|
220
|
+
res.ensureOnAborted()
|
|
221
|
+
if (handler && typeof handler === 'object') {
|
|
222
|
+
const processedMiddleware = preProcessMiddleware(handler.middleware)
|
|
223
|
+
if (handler.handlers && typeof handler.handlers[sReq.method] === 'function') {
|
|
224
|
+
const h = handler.handlers[sReq.method]
|
|
225
|
+
if ('statusCodeOverride' in handler) {
|
|
226
|
+
h.statusCodeOverride = handler.statusCodeOverride
|
|
227
|
+
}
|
|
228
|
+
handleRequest(sReq, res, h, processedMiddleware, config)
|
|
229
|
+
.catch(e => {
|
|
262
230
|
log.error('Unexpected exception during request handling', e)
|
|
263
231
|
if (!res.writableEnded) {
|
|
264
232
|
res.statusCode = 500
|
|
265
233
|
res.end()
|
|
266
234
|
}
|
|
267
235
|
})
|
|
268
|
-
} else {
|
|
269
|
-
res.statusCode = 404
|
|
270
|
-
res.end()
|
|
271
|
-
}
|
|
272
236
|
} else {
|
|
273
237
|
res.statusCode = 404
|
|
274
238
|
res.end()
|
|
275
239
|
}
|
|
276
|
-
}
|
|
240
|
+
} else {
|
|
241
|
+
res.statusCode = 404
|
|
242
|
+
res.end()
|
|
243
|
+
}
|
|
277
244
|
} catch (e) {
|
|
278
245
|
log.error('Failed handling request', e)
|
|
279
246
|
}
|
package/src/index.mjs
CHANGED
package/src/routes.mjs
CHANGED
|
@@ -113,6 +113,10 @@ const buildJSHandlerRoute = async (name, filePath, urlPath, inheritedMiddleware,
|
|
|
113
113
|
}
|
|
114
114
|
const route = {
|
|
115
115
|
pathParameters,
|
|
116
|
+
paramToIndex: pathParameters.reduce((acc, cur, i) => {
|
|
117
|
+
acc[cur] = i
|
|
118
|
+
return acc
|
|
119
|
+
}, {}),
|
|
116
120
|
urlPath: `${urlPath}/${getPathPart(name)}`,
|
|
117
121
|
filePath,
|
|
118
122
|
handlers: {}
|
|
@@ -163,6 +167,10 @@ const buildStaticRoutes = (name, filePath, urlPath, inheritedMiddleware, pathPar
|
|
|
163
167
|
const contentType = getContentTypeByExtension(name, config.staticContentTypes)
|
|
164
168
|
const route = {
|
|
165
169
|
pathParameters,
|
|
170
|
+
paramToIndex: pathParameters.reduce((acc, cur, i) => {
|
|
171
|
+
acc[cur] = i
|
|
172
|
+
return acc
|
|
173
|
+
}, {}),
|
|
166
174
|
urlPath: `${urlPath}/${getPathPart(name)}`,
|
|
167
175
|
filePath,
|
|
168
176
|
handlers: createStaticHandler(filePath, contentType, config.cacheStatic, config.staticCacheControl),
|
package/src/server.mjs
CHANGED
|
@@ -102,7 +102,7 @@ export async function startServer (config) {
|
|
|
102
102
|
if (method === 'WEBSOCKET') {
|
|
103
103
|
theHandler = route.handlers[method]
|
|
104
104
|
} else {
|
|
105
|
-
theHandler = createHandler(route.handlers[method], route.middleware, route.
|
|
105
|
+
theHandler = createHandler(route.handlers[method], route.middleware, route.paramToIndex, config, route.urlPath)
|
|
106
106
|
}
|
|
107
107
|
app[appMethods[method]](route.urlPath, theHandler)
|
|
108
108
|
if (hadSlash && config.serveRoutesWithSlash) {
|