@nxtedition/nxt-undici 1.0.8 → 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() {
@@ -65,14 +104,13 @@ class Readable extends stream.Readable {
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'),
@@ -159,7 +198,8 @@ async function request(url, opts) {
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)
@@ -220,16 +260,13 @@ async function request(url, opts) {
220
260
  const contentLength = Number(headers['content-length'] ?? headers['Content-Length'])
221
261
 
222
262
  this.body = new Readable({
223
- read: resume,
263
+ resume,
264
+ abort: this.abort,
224
265
  highWaterMark: 128 * 1024,
225
266
  statusCode,
226
267
  statusMessage,
227
268
  headers,
228
269
  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
270
  })
234
271
 
235
272
  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.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
- }