@nxtedition/nxt-undici 1.0.4 → 1.0.6
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/lib/index.js +109 -96
- package/lib/interceptor/content.js +7 -6
- package/lib/interceptor/log.js +5 -18
- package/lib/interceptor/proxy.js +14 -12
- package/lib/interceptor/redirect.js +18 -24
- package/lib/interceptor/request-id.js +29 -0
- package/lib/interceptor/response-retry.js +1 -1
- package/lib/interceptor/signal.js +6 -2
- package/lib/utils.js +34 -0
- package/package.json +4 -1
package/lib/index.js
CHANGED
|
@@ -4,6 +4,8 @@ const undici = require('undici')
|
|
|
4
4
|
const stream = require('stream')
|
|
5
5
|
const { parseHeaders } = require('./utils')
|
|
6
6
|
|
|
7
|
+
const dispatcherCache = new WeakMap()
|
|
8
|
+
|
|
7
9
|
// https://github.com/fastify/fastify/blob/main/lib/reqIdGenFactory.js
|
|
8
10
|
// 2,147,483,647 (2^31 − 1) stands for max SMI value (an internal optimization of V8).
|
|
9
11
|
// With this upper bound, if you'll be generating 1k ids/sec, you're going to hit it in ~25 days.
|
|
@@ -87,6 +89,7 @@ const dispatchers = {
|
|
|
87
89
|
signal: require('./interceptor/signal.js'),
|
|
88
90
|
proxy: require('./interceptor/proxy.js'),
|
|
89
91
|
cache: require('./interceptor/cache.js'),
|
|
92
|
+
requestId: require('./interceptor/request-id.js'),
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
async function request(url, opts) {
|
|
@@ -114,10 +117,9 @@ async function request(url, opts) {
|
|
|
114
117
|
headers = opts.headers
|
|
115
118
|
}
|
|
116
119
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
'user-agent':
|
|
120
|
-
...headers,
|
|
120
|
+
const userAgent = opts.userAgent ?? globalThis.userAgent
|
|
121
|
+
if (userAgent && headers?.['user-agent'] !== userAgent) {
|
|
122
|
+
headers = { 'user-agent': userAgent, ...headers }
|
|
121
123
|
}
|
|
122
124
|
|
|
123
125
|
if (method === 'CONNECT') {
|
|
@@ -125,127 +127,138 @@ async function request(url, opts) {
|
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
if (
|
|
130
|
+
headers != null &&
|
|
128
131
|
(method === 'HEAD' || method === 'GET') &&
|
|
129
132
|
(parseInt(headers['content-length']) > 0 || headers['transfer-encoding'])
|
|
130
133
|
) {
|
|
131
134
|
throw new createError.BadRequest('HEAD and GET cannot have body')
|
|
132
135
|
}
|
|
133
136
|
|
|
134
|
-
opts = {
|
|
135
|
-
url,
|
|
136
|
-
method,
|
|
137
|
-
body: opts.body,
|
|
138
|
-
headers,
|
|
139
|
-
origin: url.origin,
|
|
140
|
-
path: url.path ? url.path : url.search ? `${url.pathname}${url.search ?? ''}` : url.pathname,
|
|
141
|
-
reset: opts.reset ?? false,
|
|
142
|
-
headersTimeout: opts.headersTimeout,
|
|
143
|
-
bodyTimeout: opts.bodyTimeout,
|
|
144
|
-
idempotent,
|
|
145
|
-
signal: opts.signal,
|
|
146
|
-
retry: opts.retry ?? 8,
|
|
147
|
-
proxy: opts.proxy,
|
|
148
|
-
cache: opts.cache,
|
|
149
|
-
upgrade: opts.upgrade,
|
|
150
|
-
follow: { count: opts.maxRedirections ?? 8, ...opts.redirect, ...opts.follow },
|
|
151
|
-
logger: opts.logger,
|
|
152
|
-
maxRedirections: 0, // Disable undici's redirect handling.
|
|
153
|
-
}
|
|
154
|
-
|
|
155
137
|
const expectsPayload = opts.method === 'PUT' || opts.method === 'POST' || opts.method === 'PATCH'
|
|
156
138
|
|
|
157
|
-
if (
|
|
139
|
+
if (headers != null && headers['content-length'] === '0' && !expectsPayload) {
|
|
158
140
|
// https://tools.ietf.org/html/rfc7230#section-3.3.2
|
|
159
141
|
// A user agent SHOULD NOT send a Content-Length header field when
|
|
160
142
|
// the request message does not contain a payload body and the method
|
|
161
143
|
// semantics do not anticipate such a body.
|
|
162
144
|
|
|
163
145
|
// undici will error if provided an unexpected content-length: 0 header.
|
|
164
|
-
|
|
146
|
+
headers = { ...headers }
|
|
147
|
+
delete headers['content-length']
|
|
165
148
|
}
|
|
166
149
|
|
|
167
150
|
const dispatcher = opts.dispatcher ?? undici.getGlobalDispatcher()
|
|
168
151
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
152
|
+
let dispatch = dispatcherCache.get(dispatcher)
|
|
153
|
+
if (dispatch == null) {
|
|
154
|
+
dispatch = (opts, handler) => dispatcher.dispatch(opts, handler)
|
|
172
155
|
dispatch = dispatchers.catch(dispatch)
|
|
173
156
|
dispatch = dispatchers.abort(dispatch)
|
|
174
157
|
dispatch = dispatchers.log(dispatch)
|
|
175
|
-
dispatch =
|
|
176
|
-
dispatch =
|
|
177
|
-
dispatch =
|
|
178
|
-
dispatch =
|
|
158
|
+
dispatch = dispatchers.requestId(dispatch)
|
|
159
|
+
dispatch = dispatchers.responseRetry(dispatch)
|
|
160
|
+
dispatch = dispatchers.responseStatusRetry(dispatch)
|
|
161
|
+
dispatch = dispatchers.responseBodyRetry(dispatch)
|
|
162
|
+
dispatch = dispatchers.content(dispatch)
|
|
179
163
|
dispatch = dispatchers.redirect(dispatch)
|
|
180
164
|
dispatch = dispatchers.signal(dispatch)
|
|
181
|
-
dispatch =
|
|
165
|
+
dispatch = dispatchers.cache(dispatch)
|
|
182
166
|
dispatch = dispatchers.proxy(dispatch)
|
|
167
|
+
dispatcherCache.set(dispatcher, dispatch)
|
|
168
|
+
}
|
|
183
169
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
170
|
+
return new Promise((resolve, reject) =>
|
|
171
|
+
dispatch(
|
|
172
|
+
{
|
|
173
|
+
id: opts.id ?? genReqId(),
|
|
174
|
+
url,
|
|
175
|
+
method,
|
|
176
|
+
body: opts.body,
|
|
177
|
+
headers,
|
|
178
|
+
origin: url.origin,
|
|
179
|
+
path: url.path ?? (url.search ? `${url.pathname}${url.search ?? ''}` : url.pathname),
|
|
180
|
+
query: opts.query,
|
|
181
|
+
reset: opts.reset ?? false,
|
|
182
|
+
headersTimeout: opts.headersTimeout,
|
|
183
|
+
bodyTimeout: opts.bodyTimeout,
|
|
184
|
+
idempotent,
|
|
185
|
+
signal: opts.signal,
|
|
186
|
+
retry: opts.retry ?? 8,
|
|
187
|
+
proxy: opts.proxy,
|
|
188
|
+
cache: opts.cache,
|
|
189
|
+
upgrade: opts.upgrade,
|
|
190
|
+
follow: opts.follow ?? 8,
|
|
191
|
+
logger: opts.logger,
|
|
191
192
|
},
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
{
|
|
194
|
+
resolve,
|
|
195
|
+
reject,
|
|
196
|
+
logger: opts.logger,
|
|
197
|
+
/** @type {Function | null} */ abort: null,
|
|
198
|
+
/** @type {stream.Readable | null} */ body: null,
|
|
199
|
+
onConnect(abort) {
|
|
200
|
+
this.abort = abort
|
|
201
|
+
},
|
|
202
|
+
onUpgrade(statusCode, rawHeaders, socket) {
|
|
203
|
+
const headers = parseHeaders(rawHeaders)
|
|
194
204
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const headers = parseHeaders(rawHeaders)
|
|
205
|
-
|
|
206
|
-
if (statusCode >= 400) {
|
|
207
|
-
this.abort(createError(statusCode, { headers }))
|
|
208
|
-
} else {
|
|
209
|
-
assert(statusCode >= 200)
|
|
210
|
-
|
|
211
|
-
const contentLength = Number(headers['content-length'] ?? headers['Content-Length'])
|
|
212
|
-
|
|
213
|
-
this.body = new Readable({
|
|
214
|
-
read: resume,
|
|
215
|
-
highWaterMark: 128 * 1024,
|
|
216
|
-
statusCode,
|
|
217
|
-
statusMessage,
|
|
218
|
-
headers,
|
|
219
|
-
size: Number.isFinite(contentLength) ? contentLength : null,
|
|
220
|
-
}).on('error', (err) => {
|
|
221
|
-
if (this.logger && this.body?.listenerCount('error') === 1) {
|
|
222
|
-
this.logger.error({ err }, 'unhandled response body error')
|
|
223
|
-
}
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
this.resolve(this.body)
|
|
227
|
-
this.resolve = null
|
|
228
|
-
}
|
|
205
|
+
if (statusCode !== 101) {
|
|
206
|
+
this.abort(createError(statusCode, { headers }))
|
|
207
|
+
} else {
|
|
208
|
+
this.resolve({ headers, socket })
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
onBodySent(chunk) {},
|
|
212
|
+
onHeaders(statusCode, rawHeaders, resume, statusMessage) {
|
|
213
|
+
assert(this.abort)
|
|
229
214
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
215
|
+
const headers = parseHeaders(rawHeaders)
|
|
216
|
+
|
|
217
|
+
if (statusCode >= 400) {
|
|
218
|
+
this.abort(createError(statusCode, { headers }))
|
|
219
|
+
} else {
|
|
220
|
+
assert(statusCode >= 200)
|
|
221
|
+
|
|
222
|
+
const contentLength = Number(headers['content-length'] ?? headers['Content-Length'])
|
|
223
|
+
|
|
224
|
+
this.body = new Readable({
|
|
225
|
+
read: resume,
|
|
226
|
+
highWaterMark: 128 * 1024,
|
|
227
|
+
statusCode,
|
|
228
|
+
statusMessage,
|
|
229
|
+
headers,
|
|
230
|
+
size: Number.isFinite(contentLength) ? contentLength : null,
|
|
231
|
+
}).on('error', (err) => {
|
|
232
|
+
if (this.logger && this.body?.listenerCount('error') === 1) {
|
|
233
|
+
this.logger.error({ err }, 'unhandled response body error')
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
this.resolve(this.body)
|
|
238
|
+
this.resolve = null
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return false
|
|
242
|
+
},
|
|
243
|
+
onData(chunk) {
|
|
244
|
+
assert(this.body)
|
|
245
|
+
return this.body.push(chunk)
|
|
246
|
+
},
|
|
247
|
+
onComplete() {
|
|
248
|
+
assert(this.body)
|
|
249
|
+
this.body.push(null)
|
|
250
|
+
},
|
|
251
|
+
onError(err) {
|
|
252
|
+
if (this.body) {
|
|
253
|
+
this.body.destroy(err)
|
|
254
|
+
} else {
|
|
255
|
+
this.reject(err)
|
|
256
|
+
this.reject = null
|
|
257
|
+
}
|
|
258
|
+
},
|
|
246
259
|
},
|
|
247
|
-
|
|
248
|
-
|
|
260
|
+
),
|
|
261
|
+
)
|
|
249
262
|
}
|
|
250
263
|
|
|
251
264
|
module.exports = { request }
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const crypto = require('node:crypto')
|
|
2
2
|
const stream = require('node:stream')
|
|
3
|
-
const assert = require('node:assert')
|
|
4
3
|
const { findHeader, isStream } = require('../utils')
|
|
5
4
|
|
|
6
5
|
class Handler {
|
|
@@ -8,7 +7,6 @@ class Handler {
|
|
|
8
7
|
this.handler = handler
|
|
9
8
|
this.md5 = null
|
|
10
9
|
this.length = null
|
|
11
|
-
|
|
12
10
|
this.hasher = null
|
|
13
11
|
this.pos = 0
|
|
14
12
|
}
|
|
@@ -28,9 +26,7 @@ class Handler {
|
|
|
28
26
|
onHeaders(statusCode, rawHeaders, resume, statusMessage) {
|
|
29
27
|
this.md5 = findHeader(rawHeaders, 'content-md5')
|
|
30
28
|
this.length = findHeader(rawHeaders, 'content-length')
|
|
31
|
-
|
|
32
29
|
this.hasher = this.md5 != null ? crypto.createHash('md5') : null
|
|
33
|
-
|
|
34
30
|
return this.handler.onHeaders(statusCode, rawHeaders, resume, statusMessage)
|
|
35
31
|
}
|
|
36
32
|
|
|
@@ -67,6 +63,11 @@ class Handler {
|
|
|
67
63
|
}
|
|
68
64
|
|
|
69
65
|
module.exports = (dispatch) => (opts, handler) => {
|
|
66
|
+
if (opts.upgrade) {
|
|
67
|
+
return dispatch(opts, handler)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// TODO (fix): case-insensitive check?
|
|
70
71
|
const md5 = opts.headers?.['content-md5'] ?? opts.headers?.['Content-MD5']
|
|
71
72
|
const length = opts.headers?.['content-lenght'] ?? opts.headers?.['Content-Length']
|
|
72
73
|
|
|
@@ -86,7 +87,7 @@ module.exports = (dispatch) => (opts, handler) => {
|
|
|
86
87
|
transform(chunk, encoding, callback) {
|
|
87
88
|
pos += chunk.length
|
|
88
89
|
hasher?.update(chunk)
|
|
89
|
-
callback(null)
|
|
90
|
+
callback(null, chunk)
|
|
90
91
|
},
|
|
91
92
|
final(callback) {
|
|
92
93
|
const hash = hasher?.digest('base64')
|
|
@@ -133,7 +134,7 @@ module.exports = (dispatch) => (opts, handler) => {
|
|
|
133
134
|
})
|
|
134
135
|
}
|
|
135
136
|
} else {
|
|
136
|
-
|
|
137
|
+
throw new Error('not implemented')
|
|
137
138
|
}
|
|
138
139
|
|
|
139
140
|
return dispatch(opts, new Handler(opts, { handler }))
|
package/lib/interceptor/log.js
CHANGED
|
@@ -1,26 +1,13 @@
|
|
|
1
1
|
const { parseHeaders } = require('../utils')
|
|
2
2
|
const { performance } = require('perf_hooks')
|
|
3
3
|
|
|
4
|
-
// https://github.com/fastify/fastify/blob/main/lib/reqIdGenFactory.js
|
|
5
|
-
// 2,147,483,647 (2^31 − 1) stands for max SMI value (an internal optimization of V8).
|
|
6
|
-
// With this upper bound, if you'll be generating 1k ids/sec, you're going to hit it in ~25 days.
|
|
7
|
-
// This is very likely to happen in real-world applications, hence the limit is enforced.
|
|
8
|
-
// Growing beyond this value will make the id generation slower and cause a deopt.
|
|
9
|
-
// In the worst cases, it will become a float, losing accuracy.
|
|
10
|
-
const maxInt = 2147483647
|
|
11
|
-
let nextReqId = Math.floor(Math.random() * maxInt)
|
|
12
|
-
function genReqId() {
|
|
13
|
-
nextReqId = (nextReqId + 1) & maxInt
|
|
14
|
-
return `req-${nextReqId.toString(36)}`
|
|
15
|
-
}
|
|
16
|
-
|
|
17
4
|
class Handler {
|
|
18
5
|
constructor(opts, { handler }) {
|
|
19
6
|
this.handler = handler
|
|
20
|
-
this.opts = opts
|
|
7
|
+
this.opts = opts
|
|
21
8
|
this.abort = null
|
|
22
9
|
this.aborted = false
|
|
23
|
-
this.logger = opts.logger.child({ ureq:
|
|
10
|
+
this.logger = opts.logger.child({ ureq: opts })
|
|
24
11
|
this.pos = 0
|
|
25
12
|
this.startTime = 0
|
|
26
13
|
}
|
|
@@ -28,7 +15,7 @@ class Handler {
|
|
|
28
15
|
onConnect(abort) {
|
|
29
16
|
this.abort = abort
|
|
30
17
|
this.startTime = performance.now()
|
|
31
|
-
this.logger.debug(
|
|
18
|
+
this.logger.debug('upstream request started')
|
|
32
19
|
this.handler.onConnect((reason) => {
|
|
33
20
|
this.aborted = true
|
|
34
21
|
this.abort(reason)
|
|
@@ -36,9 +23,9 @@ class Handler {
|
|
|
36
23
|
}
|
|
37
24
|
|
|
38
25
|
onUpgrade(statusCode, rawHeaders, socket) {
|
|
39
|
-
this.logger.debug(
|
|
26
|
+
this.logger.debug('upstream request upgraded')
|
|
40
27
|
socket.on('close', () => {
|
|
41
|
-
this.logger.debug(
|
|
28
|
+
this.logger.debug('upstream request socket closed')
|
|
42
29
|
})
|
|
43
30
|
return this.handler.onUpgrade(statusCode, rawHeaders, socket)
|
|
44
31
|
}
|
package/lib/interceptor/proxy.js
CHANGED
|
@@ -102,7 +102,7 @@ const HOP_EXPR =
|
|
|
102
102
|
function forEachHeader(headers, fn) {
|
|
103
103
|
if (Array.isArray(headers)) {
|
|
104
104
|
for (let n = 0; n < headers.length; n += 2) {
|
|
105
|
-
fn(headers[n + 0]
|
|
105
|
+
fn(headers[n + 0], headers[n + 1])
|
|
106
106
|
}
|
|
107
107
|
} else {
|
|
108
108
|
for (const [key, val] of Object.entries(headers)) {
|
|
@@ -123,16 +123,16 @@ function reduceHeaders({ headers, proxyName, httpVersion, socket }, fn, acc) {
|
|
|
123
123
|
|
|
124
124
|
forEachHeader(headers, (key, val) => {
|
|
125
125
|
const len = key.length
|
|
126
|
-
if (len === 3 && !via && key.toLowerCase() === 'via') {
|
|
127
|
-
via = val
|
|
128
|
-
} else if (len === 4 && !host && key.toLowerCase() === 'host') {
|
|
129
|
-
host = val
|
|
130
|
-
} else if (len === 9 && !forwarded && key.toLowerCase() === 'forwarded') {
|
|
131
|
-
forwarded = val
|
|
132
|
-
} else if (len === 10 && !connection && key.toLowerCase() === 'connection') {
|
|
133
|
-
connection = val
|
|
134
|
-
} else if (len === 10 && !authority && key.toLowerCase() === ':authority') {
|
|
135
|
-
authority = val
|
|
126
|
+
if (len === 3 && !via && key.toString().toLowerCase() === 'via') {
|
|
127
|
+
via = val.toString()
|
|
128
|
+
} else if (len === 4 && !host && key.toString().toLowerCase() === 'host') {
|
|
129
|
+
host = val.toString()
|
|
130
|
+
} else if (len === 9 && !forwarded && key.toString().toLowerCase() === 'forwarded') {
|
|
131
|
+
forwarded = val.toString()
|
|
132
|
+
} else if (len === 10 && !connection && key.toString().toLowerCase() === 'connection') {
|
|
133
|
+
connection = val.toString()
|
|
134
|
+
} else if (len === 10 && !authority && key.toString().toLowerCase() === ':authority') {
|
|
135
|
+
authority = val.toString()
|
|
136
136
|
}
|
|
137
137
|
})
|
|
138
138
|
|
|
@@ -142,8 +142,10 @@ function reduceHeaders({ headers, proxyName, httpVersion, socket }, fn, acc) {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
forEachHeader(headers, (key, val) => {
|
|
145
|
+
key = key.toString()
|
|
146
|
+
|
|
145
147
|
if (key.charAt(0) !== ':' && !remove.includes(key) && !HOP_EXPR.test(key)) {
|
|
146
|
-
acc = fn(acc, key, val)
|
|
148
|
+
acc = fn(acc, key, val.toString())
|
|
147
149
|
}
|
|
148
150
|
})
|
|
149
151
|
|
|
@@ -11,6 +11,7 @@ class Handler {
|
|
|
11
11
|
this.abort = null
|
|
12
12
|
this.aborted = false
|
|
13
13
|
this.reason = null
|
|
14
|
+
this.maxCount = Number.isFinite(opts.follow) ? opts.follow : opts.follow?.count ?? 0
|
|
14
15
|
|
|
15
16
|
this.count = 0
|
|
16
17
|
this.location = null
|
|
@@ -34,11 +35,15 @@ class Handler {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
onUpgrade(statusCode, headers, socket) {
|
|
37
|
-
this.handler.onUpgrade(statusCode, headers, socket)
|
|
38
|
+
return this.handler.onUpgrade(statusCode, headers, socket)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
onBodySent(chunk) {
|
|
42
|
+
return this.handler.onBodySent(chunk)
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
onError(error) {
|
|
41
|
-
this.handler.onError(error)
|
|
46
|
+
return this.handler.onError(error)
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
onHeaders(statusCode, headers, resume, statusText) {
|
|
@@ -53,7 +58,7 @@ class Handler {
|
|
|
53
58
|
this.location = findHeader(headers, 'location')
|
|
54
59
|
|
|
55
60
|
if (!this.location) {
|
|
56
|
-
throw new Error(`Missing redirection location.`)
|
|
61
|
+
throw new Error(`Missing redirection location .`)
|
|
57
62
|
}
|
|
58
63
|
|
|
59
64
|
this.count += 1
|
|
@@ -63,10 +68,8 @@ class Handler {
|
|
|
63
68
|
return this.handler.onHeaders(statusCode, headers, resume, statusText)
|
|
64
69
|
}
|
|
65
70
|
} else {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (this.count >= maxCount) {
|
|
69
|
-
throw new Error(`Max redirections reached: ${maxCount}.`)
|
|
71
|
+
if (this.count >= this.maxCount) {
|
|
72
|
+
throw new Error(`Max redirections reached: ${this.maxCount}.`)
|
|
70
73
|
}
|
|
71
74
|
}
|
|
72
75
|
|
|
@@ -136,13 +139,7 @@ class Handler {
|
|
|
136
139
|
|
|
137
140
|
this.dispatch(this.opts, this)
|
|
138
141
|
} else {
|
|
139
|
-
this.handler.onComplete(trailers)
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
onBodySent(chunk) {
|
|
144
|
-
if (this.handler.onBodySent) {
|
|
145
|
-
this.handler.onBodySent(chunk)
|
|
142
|
+
return this.handler.onComplete(trailers)
|
|
146
143
|
}
|
|
147
144
|
}
|
|
148
145
|
}
|
|
@@ -161,17 +158,12 @@ function shouldRemoveHeader(header, removeContent, unknownOrigin) {
|
|
|
161
158
|
|
|
162
159
|
// https://tools.ietf.org/html/rfc7231#section-6.4
|
|
163
160
|
function cleanRequestHeaders(headers, removeContent, unknownOrigin) {
|
|
164
|
-
|
|
165
|
-
if (
|
|
166
|
-
for (let i = 0; i < headers.length; i += 2) {
|
|
167
|
-
if (!shouldRemoveHeader(headers[i], removeContent, unknownOrigin)) {
|
|
168
|
-
ret.push(headers[i], headers[i + 1])
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
} else if (headers && typeof headers === 'object') {
|
|
161
|
+
let ret
|
|
162
|
+
if (headers && typeof headers === 'object') {
|
|
172
163
|
for (const key of Object.keys(headers)) {
|
|
173
164
|
if (!shouldRemoveHeader(key, removeContent, unknownOrigin)) {
|
|
174
|
-
ret
|
|
165
|
+
ret ??= {}
|
|
166
|
+
ret[key] = headers[key]
|
|
175
167
|
}
|
|
176
168
|
}
|
|
177
169
|
} else {
|
|
@@ -181,4 +173,6 @@ function cleanRequestHeaders(headers, removeContent, unknownOrigin) {
|
|
|
181
173
|
}
|
|
182
174
|
|
|
183
175
|
module.exports = (dispatch) => (opts, handler) =>
|
|
184
|
-
opts.follow
|
|
176
|
+
opts.follow != null
|
|
177
|
+
? dispatch(opts, new Handler(opts, { handler, dispatch }))
|
|
178
|
+
: dispatch(opts, handler)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// https://github.com/fastify/fastify/blob/main/lib/reqIdGenFactory.js
|
|
2
|
+
// 2,147,483,647 (2^31 − 1) stands for max SMI value (an internal optimization of V8).
|
|
3
|
+
// With this upper bound, if you'll be generating 1k ids/sec, you're going to hit it in ~25 days.
|
|
4
|
+
// This is very likely to happen in real-world applications, hence the limit is enforced.
|
|
5
|
+
// Growing beyond this value will make the id generation slower and cause a deopt.
|
|
6
|
+
// In the worst cases, it will become a float, losing accuracy.
|
|
7
|
+
const maxInt = 2147483647
|
|
8
|
+
let nextReqId = Math.floor(Math.random() * maxInt)
|
|
9
|
+
function genReqId() {
|
|
10
|
+
nextReqId = (nextReqId + 1) & maxInt
|
|
11
|
+
return `req-${nextReqId.toString(36)}`
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
module.exports = (dispatch) => (opts, handler) => {
|
|
15
|
+
let id = opts.id ?? opts.headers?.['request-id'] ?? opts.headers?.['Request-Id']
|
|
16
|
+
id = id ? `${id},${genReqId()}` : genReqId()
|
|
17
|
+
|
|
18
|
+
return dispatch(
|
|
19
|
+
{
|
|
20
|
+
...opts,
|
|
21
|
+
id,
|
|
22
|
+
headers: {
|
|
23
|
+
...opts.headers,
|
|
24
|
+
'request-id': id,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
handler,
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -34,12 +34,16 @@ class Handler {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
onComplete(rawTrailers) {
|
|
37
|
-
|
|
37
|
+
if (this.abort) {
|
|
38
|
+
this.signal.removeEventListener('abort', this.abort)
|
|
39
|
+
}
|
|
38
40
|
return this.handler.onComplete(rawTrailers)
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
onError(err) {
|
|
42
|
-
|
|
44
|
+
if (this.abort) {
|
|
45
|
+
this.signal.removeEventListener('abort', this.abort)
|
|
46
|
+
}
|
|
43
47
|
return this.handler.onError(err)
|
|
44
48
|
}
|
|
45
49
|
}
|
package/lib/utils.js
CHANGED
|
@@ -204,9 +204,43 @@ function isStream(obj) {
|
|
|
204
204
|
)
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
// based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License)
|
|
208
|
+
function isBlobLike(object) {
|
|
209
|
+
return (
|
|
210
|
+
(Blob && object instanceof Blob) ||
|
|
211
|
+
(object &&
|
|
212
|
+
typeof object === 'object' &&
|
|
213
|
+
(typeof object.stream === 'function' || typeof object.arrayBuffer === 'function') &&
|
|
214
|
+
/^(Blob|File)$/.test(object[Symbol.toStringTag]))
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function isBuffer(buffer) {
|
|
219
|
+
// See, https://github.com/mcollina/undici/pull/319
|
|
220
|
+
return buffer instanceof Uint8Array || Buffer.isBuffer(buffer)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function bodyLength(body) {
|
|
224
|
+
if (body == null) {
|
|
225
|
+
return 0
|
|
226
|
+
} else if (isStream(body)) {
|
|
227
|
+
const state = body._readableState
|
|
228
|
+
return state && state.ended === true && Number.isFinite(state.length) ? state.length : null
|
|
229
|
+
} else if (isBlobLike(body)) {
|
|
230
|
+
return body.size != null ? body.size : null
|
|
231
|
+
} else if (isBuffer(body)) {
|
|
232
|
+
return body.byteLength
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return null
|
|
236
|
+
}
|
|
237
|
+
|
|
207
238
|
module.exports = {
|
|
208
239
|
isStream,
|
|
240
|
+
isBuffer,
|
|
241
|
+
isBlobLike,
|
|
209
242
|
AbortError,
|
|
243
|
+
bodyLength,
|
|
210
244
|
parseHeaders,
|
|
211
245
|
isDisturbed,
|
|
212
246
|
parseContentRange,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxtedition/nxt-undici",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Robert Nagy <robert.nagy@boffins.se>",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
"eslint": "^8.50.0",
|
|
19
19
|
"eslint-config-prettier": "^9.0.0",
|
|
20
20
|
"eslint-config-standard": "^17.0.0",
|
|
21
|
+
"eslint-plugin-import": "^2.28.1",
|
|
22
|
+
"eslint-plugin-n": "^16.2.0",
|
|
23
|
+
"eslint-plugin-promise": "^6.1.1",
|
|
21
24
|
"husky": "^8.0.3",
|
|
22
25
|
"lint-staged": "^14.0.1",
|
|
23
26
|
"pinst": "^3.0.0",
|