@nxtedition/nxt-undici 1.0.7 → 1.0.9

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 CHANGED
@@ -2,7 +2,7 @@ const assert = require('assert')
2
2
  const createError = require('http-errors')
3
3
  const undici = require('undici')
4
4
  const stream = require('stream')
5
- const { parseHeaders } = require('./utils')
5
+ const { parseHeaders, AbortError } = require('./utils')
6
6
 
7
7
  const dispatcherCache = new WeakMap()
8
8
 
@@ -19,14 +19,53 @@ function genReqId() {
19
19
  return `req-${nextReqId.toString(36)}`
20
20
  }
21
21
 
22
+ const kAbort = Symbol('abort')
23
+ const kStatusCode = Symbol('statusCode')
24
+ const kStatusMessage = Symbol('statusMessage')
25
+ const kHeaders = Symbol('headers')
26
+ const kSize = Symbol('size')
27
+
22
28
  class Readable extends stream.Readable {
23
- constructor({ statusCode, statusMessage, headers, size, ...opts }) {
24
- super(opts)
25
- this.statusCode = statusCode
26
- this.statusMessage = statusMessage
27
- this.headers = headers
28
- this.body = this
29
- this.size = size
29
+ constructor({ statusCode, statusMessage, headers, size, abort, highWaterMark, resume }) {
30
+ super(highWaterMark ? { highWaterMark } : undefined)
31
+
32
+ this[kStatusCode] = statusCode
33
+ this[kStatusMessage] = statusMessage
34
+ this[kHeaders] = headers
35
+ this[kSize] = size
36
+ this[kAbort] = abort
37
+
38
+ this._read = resume
39
+ }
40
+
41
+ get statusCode() {
42
+ return this[kStatusCode]
43
+ }
44
+
45
+ get statusMessage() {
46
+ return this[kStatusMessage]
47
+ }
48
+
49
+ get headers() {
50
+ return this[kHeaders]
51
+ }
52
+
53
+ get size() {
54
+ return this[kSize]
55
+ }
56
+
57
+ get body() {
58
+ return this
59
+ }
60
+
61
+ _destroy(err, callback) {
62
+ if (err == null && !this.readableEnded) {
63
+ err = new AbortError()
64
+ }
65
+
66
+ this[kAbort](err)
67
+
68
+ callback(err)
30
69
  }
31
70
 
32
71
  async text() {
@@ -50,6 +89,10 @@ class Readable extends stream.Readable {
50
89
  }
51
90
 
52
91
  async arrayBuffer() {
92
+ return (await this.buffer()).buffer
93
+ }
94
+
95
+ async buffer() {
53
96
  const buffers = []
54
97
  for await (const chunk of this) {
55
98
  buffers.push(chunk)
@@ -57,22 +100,17 @@ class Readable extends stream.Readable {
57
100
  return Buffer.concat(buffers)
58
101
  }
59
102
 
60
- async buffer() {
61
- return Buffer.from(await this.arrayBuffer())
62
- }
63
-
64
103
  async dump() {
65
104
  let n = 0
66
105
  try {
67
106
  for await (const chunk of this) {
68
- // do nothing
69
107
  n += chunk.length
70
108
  if (n > 128 * 1024) {
71
109
  break
72
110
  }
73
111
  }
74
112
  } catch {
75
- this.destroy()
113
+ // Do nothing...
76
114
  }
77
115
  }
78
116
  }
@@ -80,7 +118,8 @@ class Readable extends stream.Readable {
80
118
  const dispatchers = {
81
119
  abort: require('./interceptor/abort.js'),
82
120
  catch: require('./interceptor/catch.js'),
83
- content: require('./interceptor/content.js'),
121
+ responseContent: require('./interceptor/response-content.js'),
122
+ requestContent: require('./interceptor/request-content.js'),
84
123
  log: require('./interceptor/log.js'),
85
124
  redirect: require('./interceptor/redirect.js'),
86
125
  responseBodyRetry: require('./interceptor/response-body-retry.js'),
@@ -154,12 +193,13 @@ async function request(url, opts) {
154
193
  dispatch = (opts, handler) => dispatcher.dispatch(opts, handler)
155
194
  dispatch = dispatchers.catch(dispatch)
156
195
  dispatch = dispatchers.abort(dispatch)
157
- dispatch = dispatchers.log(dispatch)
158
196
  dispatch = dispatchers.requestId(dispatch)
197
+ dispatch = dispatchers.log(dispatch)
159
198
  dispatch = dispatchers.responseRetry(dispatch)
160
199
  dispatch = dispatchers.responseStatusRetry(dispatch)
161
200
  dispatch = dispatchers.responseBodyRetry(dispatch)
162
- dispatch = dispatchers.content(dispatch)
201
+ dispatch = dispatchers.responseContent(dispatch)
202
+ dispatch = dispatchers.requestContent(dispatch)
163
203
  dispatch = dispatchers.redirect(dispatch)
164
204
  dispatch = dispatchers.signal(dispatch)
165
205
  dispatch = dispatchers.cache(dispatch)
@@ -210,8 +250,6 @@ async function request(url, opts) {
210
250
  },
211
251
  onBodySent(chunk) {},
212
252
  onHeaders(statusCode, rawHeaders, resume, statusMessage) {
213
- assert(this.abort)
214
-
215
253
  const headers = parseHeaders(rawHeaders)
216
254
 
217
255
  if (statusCode >= 400) {
@@ -222,30 +260,26 @@ async function request(url, opts) {
222
260
  const contentLength = Number(headers['content-length'] ?? headers['Content-Length'])
223
261
 
224
262
  this.body = new Readable({
225
- read: resume,
263
+ resume,
264
+ abort: this.abort,
226
265
  highWaterMark: 128 * 1024,
227
266
  statusCode,
228
267
  statusMessage,
229
268
  headers,
230
269
  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
270
  })
236
271
 
237
272
  this.resolve(this.body)
238
273
  this.resolve = null
274
+ this.reject = null
239
275
  }
240
276
 
241
277
  return false
242
278
  },
243
279
  onData(chunk) {
244
- assert(this.body)
245
280
  return this.body.push(chunk)
246
281
  },
247
282
  onComplete() {
248
- assert(this.body)
249
283
  this.body.push(null)
250
284
  },
251
285
  onError(err) {
@@ -253,6 +287,7 @@ async function request(url, opts) {
253
287
  this.body.destroy(err)
254
288
  } else {
255
289
  this.reject(err)
290
+ this.resolve = null
256
291
  this.reject = null
257
292
  }
258
293
  },
@@ -18,6 +18,10 @@ class Handler {
18
18
  return this.handler.onBodySent(chunk)
19
19
  }
20
20
 
21
+ onRequestSent() {
22
+ return this.handler.onRequestSent()
23
+ }
24
+
21
25
  onUpgrade(statusCode, rawHeaders, socket) {
22
26
  return this.handler.onUpgrade(statusCode, rawHeaders, socket)
23
27
  }
@@ -18,6 +18,14 @@ class CacheHandler {
18
18
  return this.handler.onUpgrade(statusCode, rawHeaders, socket)
19
19
  }
20
20
 
21
+ onBodySent(chunk) {
22
+ return this.handler.onBodySent(chunk)
23
+ }
24
+
25
+ onRequestSent() {
26
+ return this.handler.onRequestSent()
27
+ }
28
+
21
29
  onHeaders(statusCode, rawHeaders, resume, statusMessage) {
22
30
  // NOTE: Only cache 307 respones for now...
23
31
  if (statusCode !== 307) {
@@ -28,6 +28,14 @@ class Handler {
28
28
  }
29
29
  }
30
30
 
31
+ onRequestSent() {
32
+ try {
33
+ return this.handler.onRequestSent()
34
+ } catch (err) {
35
+ this.abort(err)
36
+ }
37
+ }
38
+
31
39
  onHeaders(statusCode, rawHeaders, resume, statusMessage) {
32
40
  try {
33
41
  return this.handler.onHeaders(statusCode, rawHeaders, resume, statusMessage)
@@ -34,6 +34,10 @@ class Handler {
34
34
  return this.handler.onBodySent(chunk)
35
35
  }
36
36
 
37
+ onRequestSent() {
38
+ return this.handler.onRequestSent()
39
+ }
40
+
37
41
  onHeaders(statusCode, rawHeaders, resume, statusMessage) {
38
42
  this.logger.debug(
39
43
  {
@@ -35,6 +35,10 @@ class Handler {
35
35
  return this.handler.onBodySent(chunk)
36
36
  }
37
37
 
38
+ onRequestSent() {
39
+ return this.handler.onRequestSent()
40
+ }
41
+
38
42
  onHeaders(statusCode, rawHeaders, resume, statusMessage) {
39
43
  return this.handler.onHeaders(
40
44
  statusCode,
@@ -42,6 +42,10 @@ class Handler {
42
42
  return this.handler.onBodySent(chunk)
43
43
  }
44
44
 
45
+ onRequestSent() {
46
+ return this.handler.onRequestSent()
47
+ }
48
+
45
49
  onError(error) {
46
50
  return this.handler.onError(error)
47
51
  }
@@ -0,0 +1,76 @@
1
+ const crypto = require('node:crypto')
2
+
3
+ class Handler {
4
+ constructor(opts, { handler, md5, length }) {
5
+ this.handler = handler
6
+ this.md5 = md5
7
+ this.length = length
8
+ this.hasher = this.md5 ? crypto.createHash('md5') : null
9
+ this.pos = 0
10
+ }
11
+
12
+ onConnect(abort) {
13
+ return this.handler.onConnect(abort)
14
+ }
15
+
16
+ onUpgrade(statusCode, rawHeaders, socket) {
17
+ return this.handler.onUpgrade(statusCode, rawHeaders, socket)
18
+ }
19
+
20
+ onBodySent(chunk) {
21
+ this.pos += chunk.length
22
+ this.hasher?.update(chunk)
23
+ return this.handler.onBodySent(chunk)
24
+ }
25
+
26
+ onRequestSent() {
27
+ const hash = this.hasher?.digest('base64')
28
+ if (this.md5 != null && hash !== this.md5) {
29
+ this.handler.onError(
30
+ Object.assign(new Error('Request Content-Length mismatch'), {
31
+ expected: this.md5,
32
+ actual: hash,
33
+ }),
34
+ )
35
+ }
36
+ if (this.length != null && this.pos !== Number(this.length)) {
37
+ return this.handler.onError(
38
+ Object.assign(new Error('Request Content-Length mismatch'), {
39
+ expected: Number(this.length),
40
+ actual: this.pos,
41
+ }),
42
+ )
43
+ }
44
+ return this.handler.onRequestSent()
45
+ }
46
+
47
+ onHeaders(statusCode, rawHeaders, resume, statusMessage) {
48
+ return this.handler.onHeaders(statusCode, rawHeaders, resume, statusMessage)
49
+ }
50
+
51
+ onData(chunk) {
52
+ return this.handler.onData(chunk)
53
+ }
54
+
55
+ onComplete(rawTrailers) {
56
+ return this.handler.onComplete(rawTrailers)
57
+ }
58
+
59
+ onError(err) {
60
+ this.handler.onError(err)
61
+ }
62
+ }
63
+
64
+ module.exports = (dispatch) => (opts, handler) => {
65
+ if (opts.upgrade) {
66
+ return dispatch(opts, handler)
67
+ }
68
+
69
+ // TODO (fix): case-insensitive check?
70
+ const md5 = opts.headers?.['content-md5'] ?? opts.headers?.['Content-MD5']
71
+ const length = opts.headers?.['content-lenght'] ?? opts.headers?.['Content-Length']
72
+
73
+ return md5 != null || length != null
74
+ ? dispatch(opts, new Handler(opts, { handler, md5, length }))
75
+ : dispatch(opts, handler)
76
+ }
@@ -44,6 +44,10 @@ class Handler {
44
44
  return this.handler.onBodySent(chunk)
45
45
  }
46
46
 
47
+ onRequestSent() {
48
+ return this.handler.onRequestSent()
49
+ }
50
+
47
51
  onHeaders(statusCode, rawHeaders, resume, statusMessage) {
48
52
  const etag = findHeader(rawHeaders, 'etag')
49
53
 
@@ -116,7 +120,6 @@ class Handler {
116
120
  return this.handler.onError(err)
117
121
  }
118
122
 
119
- // TODO (fix): abort signal?
120
123
  const retryPromise = retryFn(err, this.retryCount++, this.opts)
121
124
  if (retryPromise == null) {
122
125
  return this.handler.onError(err)
@@ -0,0 +1,69 @@
1
+ const crypto = require('node:crypto')
2
+ const { findHeader } = require('../utils')
3
+
4
+ class Handler {
5
+ constructor(opts, { handler }) {
6
+ this.handler = handler
7
+ this.md5 = null
8
+ this.length = null
9
+ this.hasher = null
10
+ this.pos = 0
11
+ }
12
+
13
+ onConnect(abort) {
14
+ return this.handler.onConnect(abort)
15
+ }
16
+
17
+ onUpgrade(statusCode, rawHeaders, socket) {
18
+ return this.handler.onUpgrade(statusCode, rawHeaders, socket)
19
+ }
20
+
21
+ onBodySent(chunk) {
22
+ return this.handler.onBodySent(chunk)
23
+ }
24
+
25
+ onRequestSent() {
26
+ return this.handler.onRequestSent()
27
+ }
28
+
29
+ onHeaders(statusCode, rawHeaders, resume, statusMessage) {
30
+ this.md5 = findHeader(rawHeaders, 'content-md5')
31
+ this.length = findHeader(rawHeaders, 'content-length')
32
+ this.hasher = this.md5 != null ? crypto.createHash('md5') : null
33
+ return this.handler.onHeaders(statusCode, rawHeaders, resume, statusMessage)
34
+ }
35
+
36
+ onData(chunk) {
37
+ this.pos += chunk.length
38
+ this.hasher?.update(chunk)
39
+ return this.handler.onData(chunk)
40
+ }
41
+
42
+ onComplete(rawTrailers) {
43
+ const hash = this.hasher?.digest('base64')
44
+ if (this.md5 != null && hash !== this.md5) {
45
+ this.handler.onError(
46
+ Object.assign(new Error('Request Content-Length mismatch'), {
47
+ expected: this.md5,
48
+ actual: hash,
49
+ }),
50
+ )
51
+ }
52
+ if (this.length != null && this.pos !== Number(this.length)) {
53
+ return this.handler.onError(
54
+ Object.assign(new Error('Request Content-Length mismatch'), {
55
+ expected: Number(this.length),
56
+ actual: this.pos,
57
+ }),
58
+ )
59
+ }
60
+ return this.handler.onComplete(rawTrailers)
61
+ }
62
+
63
+ onError(err) {
64
+ this.handler.onError(err)
65
+ }
66
+ }
67
+
68
+ module.exports = (dispatch) => (opts, handler) =>
69
+ !opts.upgrade ? dispatch(opts, new Handler(opts, { handler })) : dispatch(opts, handler)
@@ -8,6 +8,7 @@ class Handler {
8
8
  this.abort = null
9
9
  this.aborted = false
10
10
  this.reason = null
11
+ this.statusCode = 0
11
12
 
12
13
  this.retryCount = 0
13
14
  this.retryPromise = null
@@ -38,8 +39,12 @@ class Handler {
38
39
  return this.handler.onBodySent(chunk)
39
40
  }
40
41
 
42
+ onRequestSent() {
43
+ return this.handler.onRequestSent()
44
+ }
45
+
41
46
  onHeaders(statusCode, rawHeaders, resume, statusMessage) {
42
- this.aborted = true
47
+ this.statusCode = statusCode
43
48
  return this.handler.onHeaders(statusCode, rawHeaders, resume, statusMessage)
44
49
  }
45
50
 
@@ -52,11 +57,10 @@ class Handler {
52
57
  }
53
58
 
54
59
  onError(err) {
55
- if (this.aborted || isDisturbed(this.opts.body)) {
60
+ if (this.aborted || this.statusCode || isDisturbed(this.opts.body)) {
56
61
  return this.handler.onError(err)
57
62
  }
58
63
 
59
- // TODO (fix): abort signal?
60
64
  const retryPromise = retryFn(err, this.retryCount++, this.opts)
61
65
  if (retryPromise == null) {
62
66
  return this.handler.onError(err)
@@ -39,6 +39,10 @@ class Handler {
39
39
  return this.handler.onBodySent(chunk)
40
40
  }
41
41
 
42
+ onRequestSent() {
43
+ return this.handler.onRequestSent()
44
+ }
45
+
42
46
  onHeaders(statusCode, rawHeaders, resume, statusMessage) {
43
47
  if (statusCode < 400) {
44
48
  return this.handler.onHeaders(statusCode, rawHeaders, resume, statusMessage)
@@ -46,7 +50,6 @@ class Handler {
46
50
 
47
51
  const err = createError(statusCode, { headers: parseHeaders(rawHeaders) })
48
52
 
49
- // TODO (fix): abort signal?
50
53
  const retryPromise = retryFn(err, this.retryCount++, this.opts)
51
54
  if (retryPromise == null) {
52
55
  return this.handler.onHeaders(statusCode, rawHeaders, resume, statusMessage)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/nxt-undici",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "license": "MIT",
5
5
  "author": "Robert Nagy <robert.nagy@boffins.se>",
6
6
  "main": "lib/index.js",
@@ -1,141 +0,0 @@
1
- const crypto = require('node:crypto')
2
- const stream = require('node:stream')
3
- const { findHeader, isStream } = require('../utils')
4
-
5
- class Handler {
6
- constructor(opts, { handler }) {
7
- this.handler = handler
8
- this.md5 = null
9
- this.length = null
10
- this.hasher = null
11
- this.pos = 0
12
- }
13
-
14
- onConnect(abort) {
15
- return this.handler.onConnect(abort)
16
- }
17
-
18
- onUpgrade(statusCode, rawHeaders, socket) {
19
- return this.handler.onUpgrade(statusCode, rawHeaders, socket)
20
- }
21
-
22
- onBodySent(chunk) {
23
- return this.handler.onBodySent(chunk)
24
- }
25
-
26
- onHeaders(statusCode, rawHeaders, resume, statusMessage) {
27
- this.md5 = findHeader(rawHeaders, 'content-md5')
28
- this.length = findHeader(rawHeaders, 'content-length')
29
- this.hasher = this.md5 != null ? crypto.createHash('md5') : null
30
- return this.handler.onHeaders(statusCode, rawHeaders, resume, statusMessage)
31
- }
32
-
33
- onData(chunk) {
34
- this.pos += chunk.length
35
- this.hasher?.update(chunk)
36
- return this.handler.onData(chunk)
37
- }
38
-
39
- onComplete(rawTrailers) {
40
- const hash = this.hasher?.digest('base64')
41
- if (this.md5 != null && hash !== this.md5) {
42
- this.handler.onError(
43
- Object.assign(new Error('Request Content-Length mismatch'), {
44
- expected: this.md5,
45
- actual: hash,
46
- }),
47
- )
48
- }
49
- if (this.length != null && this.pos !== Number(this.length)) {
50
- return this.handler.onError(
51
- Object.assign(new Error('Request Content-Length mismatch'), {
52
- expected: Number(this.length),
53
- actual: this.pos,
54
- }),
55
- )
56
- }
57
- return this.handler.onComplete(rawTrailers)
58
- }
59
-
60
- onError(err) {
61
- this.handler.onError(err)
62
- }
63
- }
64
-
65
- module.exports = (dispatch) => (opts, handler) => {
66
- if (opts.upgrade) {
67
- return dispatch(opts, handler)
68
- }
69
-
70
- // TODO (fix): case-insensitive check?
71
- const md5 = opts.headers?.['content-md5'] ?? opts.headers?.['Content-MD5']
72
- const length = opts.headers?.['content-lenght'] ?? opts.headers?.['Content-Length']
73
-
74
- if (md5 == null && length == null) {
75
- return dispatch(opts, new Handler(opts, { handler }))
76
- }
77
-
78
- if (isStream(opts.body)) {
79
- const hasher = md5 ? crypto.createHash('md5') : null
80
- let pos = 0
81
-
82
- opts = {
83
- ...opts,
84
- body: stream.pipeline(
85
- opts.body,
86
- new stream.Transform({
87
- transform(chunk, encoding, callback) {
88
- pos += chunk.length
89
- hasher?.update(chunk)
90
- callback(null, chunk)
91
- },
92
- final(callback) {
93
- const hash = hasher?.digest('base64')
94
- if (md5 != null && hash !== md5) {
95
- callback(
96
- Object.assign(new Error('Request Content-MD5 mismatch'), {
97
- expected: md5,
98
- actual: hash,
99
- }),
100
- )
101
- } else if (length != null && pos !== Number(length)) {
102
- callback(
103
- Object.assign(new Error('Request Content-Length mismatch'), {
104
- expected: Number(length),
105
- actual: pos,
106
- }),
107
- )
108
- } else {
109
- callback(null)
110
- }
111
- },
112
- }),
113
- () => {},
114
- ),
115
- }
116
- } else if (opts.body instanceof Buffer || typeof opts.body === 'string') {
117
- const buf = Buffer.from(opts.body)
118
- const hasher = md5 ? crypto.createHash('md5') : null
119
-
120
- const hash = hasher?.update(buf).digest('base64')
121
- const pos = buf.length
122
-
123
- if (md5 != null && hash !== md5) {
124
- throw Object.assign(new Error('Request Content-MD5 mismatch'), {
125
- expected: md5,
126
- actual: hash,
127
- })
128
- }
129
-
130
- if (length != null && pos !== Number(length)) {
131
- throw Object.assign(new Error('Request Content-Length mismatch'), {
132
- expected: Number(length),
133
- actual: pos,
134
- })
135
- }
136
- } else {
137
- throw new Error('not implemented')
138
- }
139
-
140
- return dispatch(opts, new Handler(opts, { handler }))
141
- }