@nxtedition/nxt-undici 1.0.8 → 1.0.10

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,55 @@ 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
+ if (err) {
67
+ this[kAbort](err)
68
+ }
69
+
70
+ callback(err)
30
71
  }
31
72
 
32
73
  async text() {
@@ -65,14 +106,13 @@ class Readable extends stream.Readable {
65
106
  let n = 0
66
107
  try {
67
108
  for await (const chunk of this) {
68
- // do nothing
69
109
  n += chunk.length
70
110
  if (n > 128 * 1024) {
71
111
  break
72
112
  }
73
113
  }
74
114
  } catch {
75
- this.destroy()
115
+ // Do nothing...
76
116
  }
77
117
  }
78
118
  }
@@ -80,7 +120,8 @@ class Readable extends stream.Readable {
80
120
  const dispatchers = {
81
121
  abort: require('./interceptor/abort.js'),
82
122
  catch: require('./interceptor/catch.js'),
83
- content: require('./interceptor/content.js'),
123
+ responseContent: require('./interceptor/response-content.js'),
124
+ requestContent: require('./interceptor/request-content.js'),
84
125
  log: require('./interceptor/log.js'),
85
126
  redirect: require('./interceptor/redirect.js'),
86
127
  responseBodyRetry: require('./interceptor/response-body-retry.js'),
@@ -159,7 +200,8 @@ async function request(url, opts) {
159
200
  dispatch = dispatchers.responseRetry(dispatch)
160
201
  dispatch = dispatchers.responseStatusRetry(dispatch)
161
202
  dispatch = dispatchers.responseBodyRetry(dispatch)
162
- dispatch = dispatchers.content(dispatch)
203
+ dispatch = dispatchers.responseContent(dispatch)
204
+ dispatch = dispatchers.requestContent(dispatch)
163
205
  dispatch = dispatchers.redirect(dispatch)
164
206
  dispatch = dispatchers.signal(dispatch)
165
207
  dispatch = dispatchers.cache(dispatch)
@@ -220,16 +262,13 @@ async function request(url, opts) {
220
262
  const contentLength = Number(headers['content-length'] ?? headers['Content-Length'])
221
263
 
222
264
  this.body = new Readable({
223
- read: resume,
265
+ resume,
266
+ abort: this.abort,
224
267
  highWaterMark: 128 * 1024,
225
268
  statusCode,
226
269
  statusMessage,
227
270
  headers,
228
271
  size: Number.isFinite(contentLength) ? contentLength : null,
229
- }).on('error', (err) => {
230
- if (this.logger && this.body?.listenerCount('error') === 1) {
231
- this.logger.error({ err }, 'unhandled response body error')
232
- }
233
272
  })
234
273
 
235
274
  this.resolve(this.body)
@@ -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
 
@@ -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)
@@ -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
  this.statusCode = statusCode
44
48
  return this.handler.onHeaders(statusCode, rawHeaders, resume, statusMessage)
@@ -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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxtedition/nxt-undici",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
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
- }