@nxtedition/nxt-undici 2.0.20 → 2.0.22
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/errors.js +214 -0
- package/lib/index.js +4 -121
- package/lib/readable.js +338 -0
- package/lib/utils.js +4 -6
- package/package.json +1 -1
package/lib/errors.js
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
export class UndiciError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message)
|
|
4
|
+
this.name = 'UndiciError'
|
|
5
|
+
this.code = 'UND_ERR'
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class ConnectTimeoutError extends UndiciError {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message)
|
|
12
|
+
Error.captureStackTrace(this, ConnectTimeoutError)
|
|
13
|
+
this.name = 'ConnectTimeoutError'
|
|
14
|
+
this.message = message || 'Connect Timeout Error'
|
|
15
|
+
this.code = 'UND_ERR_CONNECT_TIMEOUT'
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class HeadersTimeoutError extends UndiciError {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message)
|
|
22
|
+
Error.captureStackTrace(this, HeadersTimeoutError)
|
|
23
|
+
this.name = 'HeadersTimeoutError'
|
|
24
|
+
this.message = message || 'Headers Timeout Error'
|
|
25
|
+
this.code = 'UND_ERR_HEADERS_TIMEOUT'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class HeadersOverflowError extends UndiciError {
|
|
30
|
+
constructor(message) {
|
|
31
|
+
super(message)
|
|
32
|
+
Error.captureStackTrace(this, HeadersOverflowError)
|
|
33
|
+
this.name = 'HeadersOverflowError'
|
|
34
|
+
this.message = message || 'Headers Overflow Error'
|
|
35
|
+
this.code = 'UND_ERR_HEADERS_OVERFLOW'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class BodyTimeoutError extends UndiciError {
|
|
40
|
+
constructor(message) {
|
|
41
|
+
super(message)
|
|
42
|
+
Error.captureStackTrace(this, BodyTimeoutError)
|
|
43
|
+
this.name = 'BodyTimeoutError'
|
|
44
|
+
this.message = message || 'Body Timeout Error'
|
|
45
|
+
this.code = 'UND_ERR_BODY_TIMEOUT'
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class ResponseStatusCodeError extends UndiciError {
|
|
50
|
+
constructor(message, statusCode, headers, body) {
|
|
51
|
+
super(message)
|
|
52
|
+
Error.captureStackTrace(this, ResponseStatusCodeError)
|
|
53
|
+
this.name = 'ResponseStatusCodeError'
|
|
54
|
+
this.message = message || 'Response Status Code Error'
|
|
55
|
+
this.code = 'UND_ERR_RESPONSE_STATUS_CODE'
|
|
56
|
+
this.body = body
|
|
57
|
+
this.status = statusCode
|
|
58
|
+
this.statusCode = statusCode
|
|
59
|
+
this.headers = headers
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class InvalidArgumentError extends UndiciError {
|
|
64
|
+
constructor(message) {
|
|
65
|
+
super(message)
|
|
66
|
+
Error.captureStackTrace(this, InvalidArgumentError)
|
|
67
|
+
this.name = 'InvalidArgumentError'
|
|
68
|
+
this.message = message || 'Invalid Argument Error'
|
|
69
|
+
this.code = 'UND_ERR_INVALID_ARG'
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class InvalidReturnValueError extends UndiciError {
|
|
74
|
+
constructor(message) {
|
|
75
|
+
super(message)
|
|
76
|
+
Error.captureStackTrace(this, InvalidReturnValueError)
|
|
77
|
+
this.name = 'InvalidReturnValueError'
|
|
78
|
+
this.message = message || 'Invalid Return Value Error'
|
|
79
|
+
this.code = 'UND_ERR_INVALID_RETURN_VALUE'
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export class AbortError extends UndiciError {
|
|
84
|
+
constructor(message) {
|
|
85
|
+
super(message)
|
|
86
|
+
Error.captureStackTrace(this, AbortError)
|
|
87
|
+
this.name = 'AbortError'
|
|
88
|
+
this.message = message || 'The operation was aborted'
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export class RequestAbortedError extends AbortError {
|
|
93
|
+
constructor(message) {
|
|
94
|
+
super(message)
|
|
95
|
+
Error.captureStackTrace(this, RequestAbortedError)
|
|
96
|
+
this.name = 'AbortError'
|
|
97
|
+
this.message = message || 'Request aborted'
|
|
98
|
+
this.code = 'UND_ERR_ABORTED'
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export class InformationalError extends UndiciError {
|
|
103
|
+
constructor(message) {
|
|
104
|
+
super(message)
|
|
105
|
+
Error.captureStackTrace(this, InformationalError)
|
|
106
|
+
this.name = 'InformationalError'
|
|
107
|
+
this.message = message || 'Request information'
|
|
108
|
+
this.code = 'UND_ERR_INFO'
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export class RequestContentLengthMismatchError extends UndiciError {
|
|
113
|
+
constructor(message) {
|
|
114
|
+
super(message)
|
|
115
|
+
Error.captureStackTrace(this, RequestContentLengthMismatchError)
|
|
116
|
+
this.name = 'RequestContentLengthMismatchError'
|
|
117
|
+
this.message = message || 'Request body length does not match content-length header'
|
|
118
|
+
this.code = 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH'
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export class ResponseContentLengthMismatchError extends UndiciError {
|
|
123
|
+
constructor(message) {
|
|
124
|
+
super(message)
|
|
125
|
+
Error.captureStackTrace(this, ResponseContentLengthMismatchError)
|
|
126
|
+
this.name = 'ResponseContentLengthMismatchError'
|
|
127
|
+
this.message = message || 'Response body length does not match content-length header'
|
|
128
|
+
this.code = 'UND_ERR_RES_CONTENT_LENGTH_MISMATCH'
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export class ClientDestroyedError extends UndiciError {
|
|
133
|
+
constructor(message) {
|
|
134
|
+
super(message)
|
|
135
|
+
Error.captureStackTrace(this, ClientDestroyedError)
|
|
136
|
+
this.name = 'ClientDestroyedError'
|
|
137
|
+
this.message = message || 'The client is destroyed'
|
|
138
|
+
this.code = 'UND_ERR_DESTROYED'
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export class ClientClosedError extends UndiciError {
|
|
143
|
+
constructor(message) {
|
|
144
|
+
super(message)
|
|
145
|
+
Error.captureStackTrace(this, ClientClosedError)
|
|
146
|
+
this.name = 'ClientClosedError'
|
|
147
|
+
this.message = message || 'The client is closed'
|
|
148
|
+
this.code = 'UND_ERR_CLOSED'
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export class SocketError extends UndiciError {
|
|
153
|
+
constructor(message, socket) {
|
|
154
|
+
super(message)
|
|
155
|
+
Error.captureStackTrace(this, SocketError)
|
|
156
|
+
this.name = 'SocketError'
|
|
157
|
+
this.message = message || 'Socket error'
|
|
158
|
+
this.code = 'UND_ERR_SOCKET'
|
|
159
|
+
this.socket = socket
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export class NotSupportedError extends UndiciError {
|
|
164
|
+
constructor(message) {
|
|
165
|
+
super(message)
|
|
166
|
+
Error.captureStackTrace(this, NotSupportedError)
|
|
167
|
+
this.name = 'NotSupportedError'
|
|
168
|
+
this.message = message || 'Not supported error'
|
|
169
|
+
this.code = 'UND_ERR_NOT_SUPPORTED'
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export class BalancedPoolMissingUpstreamError extends UndiciError {
|
|
174
|
+
constructor(message) {
|
|
175
|
+
super(message)
|
|
176
|
+
Error.captureStackTrace(this, NotSupportedError)
|
|
177
|
+
this.name = 'MissingUpstreamError'
|
|
178
|
+
this.message = message || 'No upstream has been added to the BalancedPool'
|
|
179
|
+
this.code = 'UND_ERR_BPL_MISSING_UPSTREAM'
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export class HTTPParserError extends Error {
|
|
184
|
+
constructor(message, code, data) {
|
|
185
|
+
super(message)
|
|
186
|
+
Error.captureStackTrace(this, HTTPParserError)
|
|
187
|
+
this.name = 'HTTPParserError'
|
|
188
|
+
this.code = code ? `HPE_${code}` : undefined
|
|
189
|
+
this.data = data ? data.toString() : undefined
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export class ResponseExceededMaxSizeError extends UndiciError {
|
|
194
|
+
constructor(message) {
|
|
195
|
+
super(message)
|
|
196
|
+
Error.captureStackTrace(this, ResponseExceededMaxSizeError)
|
|
197
|
+
this.name = 'ResponseExceededMaxSizeError'
|
|
198
|
+
this.message = message || 'Response content exceeded max size'
|
|
199
|
+
this.code = 'UND_ERR_RES_EXCEEDED_MAX_SIZE'
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export class RequestRetryError extends UndiciError {
|
|
204
|
+
constructor(message, code, { headers, data }) {
|
|
205
|
+
super(message)
|
|
206
|
+
Error.captureStackTrace(this, RequestRetryError)
|
|
207
|
+
this.name = 'RequestRetryError'
|
|
208
|
+
this.message = message || 'Request retry error'
|
|
209
|
+
this.code = 'UND_ERR_REQ_RETRY'
|
|
210
|
+
this.statusCode = code
|
|
211
|
+
this.data = data
|
|
212
|
+
this.headers = headers
|
|
213
|
+
}
|
|
214
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
|
-
import stream from 'node:stream'
|
|
3
2
|
import createError from 'http-errors'
|
|
4
3
|
import undici from 'undici'
|
|
5
4
|
import { findHeader, parseHeaders, AbortError, isStream } from './utils.js'
|
|
5
|
+
import { BodyReadable as Readable } from './readable.js'
|
|
6
6
|
import CacheableLookup from 'cacheable-lookup'
|
|
7
7
|
|
|
8
8
|
const dispatcherCache = new WeakMap()
|
|
@@ -20,125 +20,6 @@ function genReqId() {
|
|
|
20
20
|
return `req-${nextReqId.toString(36)}`
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const kAbort = Symbol('abort')
|
|
24
|
-
const kStatusCode = Symbol('statusCode')
|
|
25
|
-
const kStatusMessage = Symbol('statusMessage')
|
|
26
|
-
const kHeaders = Symbol('headers')
|
|
27
|
-
const kSize = Symbol('size')
|
|
28
|
-
const kHandler = Symbol('handler')
|
|
29
|
-
|
|
30
|
-
let ABORT_ERROR
|
|
31
|
-
|
|
32
|
-
class Readable extends stream.Readable {
|
|
33
|
-
constructor(handler, { statusCode, statusMessage, headers, size, abort, highWaterMark, resume }) {
|
|
34
|
-
super({ highWaterMark })
|
|
35
|
-
|
|
36
|
-
this[kHandler] = handler
|
|
37
|
-
this[kStatusCode] = statusCode
|
|
38
|
-
this[kStatusMessage] = statusMessage
|
|
39
|
-
this[kHeaders] = headers
|
|
40
|
-
this[kSize] = size
|
|
41
|
-
this[kAbort] = abort
|
|
42
|
-
|
|
43
|
-
this._read = resume
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
get statusCode() {
|
|
47
|
-
return this[kStatusCode]
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
get statusMessage() {
|
|
51
|
-
return this[kStatusMessage]
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
get headers() {
|
|
55
|
-
return this[kHeaders]
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
get size() {
|
|
59
|
-
return this[kSize]
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
get body() {
|
|
63
|
-
return this
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
_destroy(err, callback) {
|
|
67
|
-
if (err == null && !this.readableEnded) {
|
|
68
|
-
ABORT_ERROR ??= new AbortError()
|
|
69
|
-
err = ABORT_ERROR
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (err) {
|
|
73
|
-
this[kAbort](err)
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (this[kHandler].signal) {
|
|
77
|
-
this[kHandler].signal.removeEventListener('abort', this[kHandler].onAbort)
|
|
78
|
-
this[kHandler].signal = null
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Workaround: https://github.com/nodejs/undici/pull/2497
|
|
82
|
-
queueMicrotask(() => {
|
|
83
|
-
callback(err)
|
|
84
|
-
})
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async text() {
|
|
88
|
-
const dec = new TextDecoder()
|
|
89
|
-
let str = ''
|
|
90
|
-
|
|
91
|
-
for await (const chunk of this) {
|
|
92
|
-
if (typeof chunk === 'string') {
|
|
93
|
-
str += chunk
|
|
94
|
-
} else {
|
|
95
|
-
str += dec.decode(chunk, { stream: true })
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Flush the streaming TextDecoder so that any pending
|
|
100
|
-
// incomplete multibyte characters are handled.
|
|
101
|
-
str += dec.decode(undefined, { stream: false })
|
|
102
|
-
return str
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async json() {
|
|
106
|
-
return JSON.parse(await this.text())
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async arrayBuffer() {
|
|
110
|
-
return (await this.buffer()).buffer
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async buffer() {
|
|
114
|
-
const buffers = []
|
|
115
|
-
for await (const chunk of this) {
|
|
116
|
-
buffers.push(chunk)
|
|
117
|
-
}
|
|
118
|
-
return Buffer.concat(buffers)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
dump() {
|
|
122
|
-
return new Promise((resolve) => {
|
|
123
|
-
const rState = this._readableState
|
|
124
|
-
if (rState.closeEmitted) {
|
|
125
|
-
resolve(null)
|
|
126
|
-
} else {
|
|
127
|
-
let n = 0
|
|
128
|
-
this.on('close', () => resolve(null))
|
|
129
|
-
.on('error', () => {})
|
|
130
|
-
.on('data', (chunk) => {
|
|
131
|
-
n += chunk.length
|
|
132
|
-
if (n > 128 * 1024) {
|
|
133
|
-
this.destroy()
|
|
134
|
-
}
|
|
135
|
-
})
|
|
136
|
-
.resume()
|
|
137
|
-
}
|
|
138
|
-
})
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
23
|
const dispatchers = {
|
|
143
24
|
responseError: (await import('./interceptor/response-error.js')).default,
|
|
144
25
|
requestBodyFactory: (await import('./interceptor/request-body-factory.js')).default,
|
|
@@ -319,7 +200,8 @@ export async function request(url, opts) {
|
|
|
319
200
|
) {
|
|
320
201
|
assert(statusCode >= 200)
|
|
321
202
|
|
|
322
|
-
const contentLength = findHeader(
|
|
203
|
+
const contentLength = findHeader(headers, 'content-length')
|
|
204
|
+
const contentType = findHeader(headers, 'content-type')
|
|
323
205
|
|
|
324
206
|
this.body = new Readable(this, {
|
|
325
207
|
resume,
|
|
@@ -327,6 +209,7 @@ export async function request(url, opts) {
|
|
|
327
209
|
highWaterMark: this.highWaterMark,
|
|
328
210
|
statusCode,
|
|
329
211
|
statusMessage,
|
|
212
|
+
contentType,
|
|
330
213
|
headers,
|
|
331
214
|
size: Number.isFinite(contentLength) ? contentLength : null,
|
|
332
215
|
})
|
package/lib/readable.js
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import assert from 'node:assert'
|
|
2
|
+
import { Readable, isDisturbed } from 'node:stream'
|
|
3
|
+
import {
|
|
4
|
+
RequestAbortedError,
|
|
5
|
+
NotSupportedError,
|
|
6
|
+
InvalidArgumentError,
|
|
7
|
+
AbortError,
|
|
8
|
+
} from './errors.js'
|
|
9
|
+
|
|
10
|
+
let Blob
|
|
11
|
+
|
|
12
|
+
const kConsume = Symbol('kConsume')
|
|
13
|
+
const kReading = Symbol('kReading')
|
|
14
|
+
const kBody = Symbol('kBody')
|
|
15
|
+
const kAbort = Symbol('abort')
|
|
16
|
+
const kContentType = Symbol('kContentType')
|
|
17
|
+
|
|
18
|
+
const kStatusCode = Symbol('kStatusCode')
|
|
19
|
+
const kStatusMessage = Symbol('kStatusMessage')
|
|
20
|
+
const kHeaders = Symbol('kHeaders')
|
|
21
|
+
const kSize = Symbol('kSize')
|
|
22
|
+
const kHandler = Symbol('kHandler')
|
|
23
|
+
|
|
24
|
+
const noop = () => {}
|
|
25
|
+
|
|
26
|
+
let ABORT_ERROR
|
|
27
|
+
|
|
28
|
+
export class BodyReadable extends Readable {
|
|
29
|
+
constructor(
|
|
30
|
+
handler,
|
|
31
|
+
{ contentType = '', statusCode, statusMessage, headers, size, abort, highWaterMark, resume },
|
|
32
|
+
) {
|
|
33
|
+
super({
|
|
34
|
+
autoDestroy: true,
|
|
35
|
+
read: resume,
|
|
36
|
+
highWaterMark,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
this._readableState.dataEmitted = false
|
|
40
|
+
|
|
41
|
+
this[kHandler] = handler
|
|
42
|
+
this[kStatusCode] = statusCode
|
|
43
|
+
this[kStatusMessage] = statusMessage
|
|
44
|
+
this[kHeaders] = headers
|
|
45
|
+
this[kSize] = size
|
|
46
|
+
this[kAbort] = abort
|
|
47
|
+
|
|
48
|
+
this[kConsume] = null
|
|
49
|
+
this[kBody] = null
|
|
50
|
+
this[kContentType] = contentType
|
|
51
|
+
|
|
52
|
+
// Is stream being consumed through Readable API?
|
|
53
|
+
// This is an optimization so that we avoid checking
|
|
54
|
+
// for 'data' and 'readable' listeners in the hot path
|
|
55
|
+
// inside push().
|
|
56
|
+
this[kReading] = false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get statusCode() {
|
|
60
|
+
return this[kStatusCode]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get statusMessage() {
|
|
64
|
+
return this[kStatusMessage]
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get headers() {
|
|
68
|
+
return this[kHeaders]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get size() {
|
|
72
|
+
return this[kSize]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get body() {
|
|
76
|
+
return this
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_destroy(err, callback) {
|
|
80
|
+
if (!err && !this._readableState.endEmitted) {
|
|
81
|
+
err = ABORT_ERROR ??= new RequestAbortedError()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (err) {
|
|
85
|
+
this[kAbort]()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (this[kHandler].signal) {
|
|
89
|
+
this[kHandler].signal.removeEventListener('abort', this[kHandler].onAbort)
|
|
90
|
+
this[kHandler].signal = null
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Workaround for Node "bug". If the stream is destroyed in same
|
|
94
|
+
// tick as it is created, then a user who is waiting for a
|
|
95
|
+
// promise (i.e micro tick) for installing a 'error' listener will
|
|
96
|
+
// never get a chance and will always encounter an unhandled exception.
|
|
97
|
+
// - tick => process.nextTick(fn)
|
|
98
|
+
// - micro tick => queueMicrotask(fn)
|
|
99
|
+
queueMicrotask(() => {
|
|
100
|
+
callback(err)
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
on(ev, ...args) {
|
|
105
|
+
if (ev === 'data' || ev === 'readable') {
|
|
106
|
+
this[kReading] = true
|
|
107
|
+
}
|
|
108
|
+
return super.on(ev, ...args)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
addListener(ev, ...args) {
|
|
112
|
+
return this.on(ev, ...args)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
off(ev, ...args) {
|
|
116
|
+
const ret = super.off(ev, ...args)
|
|
117
|
+
if (ev === 'data' || ev === 'readable') {
|
|
118
|
+
this[kReading] = this.listenerCount('data') > 0 || this.listenerCount('readable') > 0
|
|
119
|
+
}
|
|
120
|
+
return ret
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
removeListener(ev, ...args) {
|
|
124
|
+
return this.off(ev, ...args)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
push(chunk) {
|
|
128
|
+
if (this[kConsume] && chunk !== null && this.readableLength === 0) {
|
|
129
|
+
consumePush(this[kConsume], chunk)
|
|
130
|
+
return this[kReading] ? super.push(chunk) : true
|
|
131
|
+
}
|
|
132
|
+
return super.push(chunk)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// https://fetch.spec.whatwg.org/#dom-body-text
|
|
136
|
+
async text() {
|
|
137
|
+
return consume(this, 'text')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// https://fetch.spec.whatwg.org/#dom-body-json
|
|
141
|
+
async json() {
|
|
142
|
+
return consume(this, 'json')
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// https://fetch.spec.whatwg.org/#dom-body-blob
|
|
146
|
+
async blob() {
|
|
147
|
+
return consume(this, 'blob')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// https://fetch.spec.whatwg.org/#dom-body-arraybuffer
|
|
151
|
+
async arrayBuffer() {
|
|
152
|
+
return consume(this, 'arrayBuffer')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// https://fetch.spec.whatwg.org/#dom-body-formdata
|
|
156
|
+
async formData() {
|
|
157
|
+
// TODO: Implement.
|
|
158
|
+
throw new NotSupportedError()
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// https://fetch.spec.whatwg.org/#dom-body-bodyused
|
|
162
|
+
get bodyUsed() {
|
|
163
|
+
return isDisturbed(this)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async dump(opts) {
|
|
167
|
+
let limit = Number.isFinite(opts?.limit) ? opts.limit : 262144
|
|
168
|
+
const signal = opts?.signal
|
|
169
|
+
|
|
170
|
+
if (signal != null && (typeof signal !== 'object' || !('aborted' in signal))) {
|
|
171
|
+
throw new InvalidArgumentError('signal must be an AbortSignal')
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
signal?.throwIfAborted()
|
|
175
|
+
|
|
176
|
+
if (this._readableState.closeEmitted) {
|
|
177
|
+
return null
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return await new Promise((resolve, reject) => {
|
|
181
|
+
const onAbort = () => {
|
|
182
|
+
this.destroy(signal.reason ?? new AbortError())
|
|
183
|
+
}
|
|
184
|
+
signal?.addEventListener('abort', onAbort)
|
|
185
|
+
|
|
186
|
+
this.on('close', function () {
|
|
187
|
+
signal?.removeEventListener('abort', onAbort)
|
|
188
|
+
if (signal?.aborted) {
|
|
189
|
+
reject(signal.reason ?? new AbortError())
|
|
190
|
+
} else {
|
|
191
|
+
resolve(null)
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
.on('error', noop)
|
|
195
|
+
.on('data', function (chunk) {
|
|
196
|
+
limit -= chunk.length
|
|
197
|
+
if (limit <= 0) {
|
|
198
|
+
this.destroy()
|
|
199
|
+
}
|
|
200
|
+
})
|
|
201
|
+
.resume()
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// https://streams.spec.whatwg.org/#readablestream-locked
|
|
207
|
+
function isLocked(self) {
|
|
208
|
+
// Consume is an implicit lock.
|
|
209
|
+
return (self[kBody] && self[kBody].locked === true) || self[kConsume]
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// https://fetch.spec.whatwg.org/#body-unusable
|
|
213
|
+
function isUnusable(self) {
|
|
214
|
+
return isDisturbed(self) || isLocked(self)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function consume(stream, type) {
|
|
218
|
+
assert(!stream[kConsume])
|
|
219
|
+
|
|
220
|
+
return new Promise((resolve, reject) => {
|
|
221
|
+
if (isUnusable(stream)) {
|
|
222
|
+
const rState = stream._readableState
|
|
223
|
+
if (rState.destroyed && rState.closeEmitted === false) {
|
|
224
|
+
stream
|
|
225
|
+
.on('error', (err) => {
|
|
226
|
+
reject(err)
|
|
227
|
+
})
|
|
228
|
+
.on('close', () => {
|
|
229
|
+
reject(new TypeError('unusable'))
|
|
230
|
+
})
|
|
231
|
+
} else {
|
|
232
|
+
reject(rState.errored ?? new TypeError('unusable'))
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
stream[kConsume] = {
|
|
236
|
+
type,
|
|
237
|
+
stream,
|
|
238
|
+
resolve,
|
|
239
|
+
reject,
|
|
240
|
+
length: 0,
|
|
241
|
+
body: [],
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
stream
|
|
245
|
+
.on('error', function (err) {
|
|
246
|
+
consumeFinish(this[kConsume], err)
|
|
247
|
+
})
|
|
248
|
+
.on('close', function () {
|
|
249
|
+
if (this[kConsume].body !== null) {
|
|
250
|
+
consumeFinish(this[kConsume], new RequestAbortedError())
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
queueMicrotask(() => consumeStart(stream[kConsume]))
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function consumeStart(consume) {
|
|
260
|
+
if (consume.body === null) {
|
|
261
|
+
return
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const { _readableState: state } = consume.stream
|
|
265
|
+
|
|
266
|
+
for (const chunk of state.buffer) {
|
|
267
|
+
consumePush(consume, chunk)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (state.endEmitted) {
|
|
271
|
+
consumeEnd(this[kConsume])
|
|
272
|
+
} else {
|
|
273
|
+
consume.stream.on('end', function () {
|
|
274
|
+
consumeEnd(this[kConsume])
|
|
275
|
+
})
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
consume.stream.resume()
|
|
279
|
+
|
|
280
|
+
while (consume.stream.read() != null) {
|
|
281
|
+
// Loop
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function consumeEnd(consume) {
|
|
286
|
+
const { type, body, resolve, stream, length } = consume
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
if (type === 'text') {
|
|
290
|
+
resolve(Buffer.concat(body).toString().toWellFormed())
|
|
291
|
+
} else if (type === 'json') {
|
|
292
|
+
resolve(JSON.parse(Buffer.concat(body)))
|
|
293
|
+
} else if (type === 'arrayBuffer') {
|
|
294
|
+
const dst = new Uint8Array(length)
|
|
295
|
+
|
|
296
|
+
let pos = 0
|
|
297
|
+
for (const buf of body) {
|
|
298
|
+
dst.set(buf, pos)
|
|
299
|
+
pos += buf.byteLength
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
resolve(dst.buffer)
|
|
303
|
+
} else if (type === 'blob') {
|
|
304
|
+
if (!Blob) {
|
|
305
|
+
Blob = require('buffer').Blob
|
|
306
|
+
}
|
|
307
|
+
resolve(new Blob(body, { type: stream[kContentType] }))
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
consumeFinish(consume)
|
|
311
|
+
} catch (err) {
|
|
312
|
+
stream.destroy(err)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function consumePush(consume, chunk) {
|
|
317
|
+
consume.length += chunk.length
|
|
318
|
+
consume.body.push(chunk)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function consumeFinish(consume, err) {
|
|
322
|
+
if (consume.body === null) {
|
|
323
|
+
return
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (err) {
|
|
327
|
+
consume.reject(err)
|
|
328
|
+
} else {
|
|
329
|
+
consume.resolve()
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
consume.type = null
|
|
333
|
+
consume.stream = null
|
|
334
|
+
consume.resolve = null
|
|
335
|
+
consume.reject = null
|
|
336
|
+
consume.length = 0
|
|
337
|
+
consume.body = null
|
|
338
|
+
}
|
package/lib/utils.js
CHANGED
|
@@ -67,15 +67,13 @@ export function findHeader(headers, name) {
|
|
|
67
67
|
for (let i = 0; i < headers.length; i += 2) {
|
|
68
68
|
const key = headers[i + 0]
|
|
69
69
|
if (key.length === len && toLowerCase(key.toString()) === name) {
|
|
70
|
-
return headers[i + 1]
|
|
70
|
+
return headers[i + 1]?.toString()
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
} else if (headers != null) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return val
|
|
78
|
-
}
|
|
74
|
+
const val = headers[name]
|
|
75
|
+
if (val !== undefined) {
|
|
76
|
+
return val
|
|
79
77
|
}
|
|
80
78
|
|
|
81
79
|
for (const key of Object.keys(headers)) {
|