undici 4.13.0 → 4.15.0
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/README.md +43 -1
- package/docs/api/BalancedPool.md +9 -4
- package/docs/api/Dispatcher.md +54 -1
- package/docs/api/MockPool.md +68 -2
- package/docs/api/Pool.md +4 -0
- package/docs/api/PoolStats.md +35 -0
- package/index.d.ts +1 -1
- package/index.js +2 -2
- package/lib/api/api-connect.js +5 -3
- package/lib/api/api-pipeline.js +7 -4
- package/lib/api/api-request.js +8 -5
- package/lib/api/api-stream.js +7 -4
- package/lib/api/api-upgrade.js +5 -3
- package/lib/balanced-pool.js +22 -5
- package/lib/client.js +5 -6
- package/lib/core/connect.js +4 -1
- package/lib/core/symbols.js +2 -0
- package/lib/core/util.js +5 -0
- package/lib/fetch/file.js +28 -0
- package/lib/fetch/index.js +5 -2
- package/lib/fetch/request.js +22 -22
- package/lib/fetch/util.js +1 -1
- package/lib/mock/mock-interceptor.js +56 -10
- package/lib/mock/mock-utils.js +9 -3
- package/lib/pool-base.js +17 -2
- package/lib/pool-stats.js +34 -0
- package/package.json +11 -5
- package/types/api.d.ts +1 -1
- package/types/dispatcher.d.ts +10 -3
- package/types/fetch.d.ts +17 -1
- package/types/mock-interceptor.d.ts +26 -2
- package/types/readable.d.ts +61 -0
package/README.md
CHANGED
|
@@ -164,7 +164,9 @@ Implements [fetch](https://fetch.spec.whatwg.org/#fetch-method).
|
|
|
164
164
|
|
|
165
165
|
Only supported on Node 16.5+.
|
|
166
166
|
|
|
167
|
-
This is [experimental](https://nodejs.org/api/documentation.html#documentation_stability_index) and is not yet fully compliant with the Fetch Standard.
|
|
167
|
+
This is [experimental](https://nodejs.org/api/documentation.html#documentation_stability_index) and is not yet fully compliant with the Fetch Standard.
|
|
168
|
+
We plan to ship breaking changes to this feature until it is out of experimental.
|
|
169
|
+
Help us improve the test coverage by following instructions at [nodejs/undici/#951](https://github.com/nodejs/undici/issues/951).
|
|
168
170
|
|
|
169
171
|
Basic usage example:
|
|
170
172
|
|
|
@@ -178,6 +180,37 @@ Basic usage example:
|
|
|
178
180
|
}
|
|
179
181
|
```
|
|
180
182
|
|
|
183
|
+
|
|
184
|
+
#### `request.body`
|
|
185
|
+
|
|
186
|
+
A body can be of the following types:
|
|
187
|
+
|
|
188
|
+
- ArrayBuffer
|
|
189
|
+
- ArrayBufferView
|
|
190
|
+
- AsyncIterables
|
|
191
|
+
- Blob
|
|
192
|
+
- Iterables
|
|
193
|
+
- String
|
|
194
|
+
- URLSearchParams
|
|
195
|
+
- FormData
|
|
196
|
+
|
|
197
|
+
In this implementation of fetch, ```request.body``` now accepts ```Async Iterables```. It is not present in the [Fetch Standard.](https://fetch.spec.whatwg.org)
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
import { fetch } from "undici";
|
|
201
|
+
|
|
202
|
+
const data = {
|
|
203
|
+
async *[Symbol.asyncIterator]() {
|
|
204
|
+
yield "hello";
|
|
205
|
+
yield "world";
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
(async () => {
|
|
210
|
+
await fetch("https://example.com", { body: data, method: 'POST' });
|
|
211
|
+
})();
|
|
212
|
+
```
|
|
213
|
+
|
|
181
214
|
#### `response.body`
|
|
182
215
|
|
|
183
216
|
Nodejs has two kinds of streams: [web streams](https://nodejs.org/dist/latest-v16.x/docs/api/webstreams.html) which follow the API of the WHATWG web standard found in browsers, and an older Node-specific [streams API](https://nodejs.org/api/stream.html). `response.body` returns a readable web stream. If you would prefer to work with a Node stream you can convert a web stream using `.fromWeb()`.
|
|
@@ -299,6 +332,15 @@ aborted.
|
|
|
299
332
|
* Refs: https://tools.ietf.org/html/rfc2616#section-8.1.2.2
|
|
300
333
|
* Refs: https://tools.ietf.org/html/rfc7230#section-6.3.2
|
|
301
334
|
|
|
335
|
+
### Manual Redirect
|
|
336
|
+
|
|
337
|
+
Since it is not possible to manually follow an HTTP redirect on server-side,
|
|
338
|
+
Undici returns the actual response instead of an `opaqueredirect` filtered one
|
|
339
|
+
when invoked with a `manual` redirect. This aligns `fetch()` with the other
|
|
340
|
+
implementations in Deno and Cloudflare Workers.
|
|
341
|
+
|
|
342
|
+
Refs: https://fetch.spec.whatwg.org/#atomic-http-redirect-handling
|
|
343
|
+
|
|
302
344
|
## Collaborators
|
|
303
345
|
|
|
304
346
|
* [__Daniele Belardi__](https://github.com/dnlup), <https://www.npmjs.com/~dnlup>
|
package/docs/api/BalancedPool.md
CHANGED
|
@@ -11,14 +11,15 @@ Requests are not guaranteed to be dispatched in order of invocation.
|
|
|
11
11
|
Arguments:
|
|
12
12
|
|
|
13
13
|
* **upstreams** `URL | string | string[]` - It should only include the **protocol, hostname, and port**.
|
|
14
|
-
* **options** `
|
|
14
|
+
* **options** `BalancedPoolOptions` (optional)
|
|
15
15
|
|
|
16
|
-
### Parameter: `
|
|
16
|
+
### Parameter: `BalancedPoolOptions`
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Extends: [`PoolOptions`](Pool.md#parameter-pooloptions)
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
* **factory** `(origin: URL, opts: Object) => Dispatcher` - Default: `(origin, opts) => new Pool(origin, opts)`
|
|
21
21
|
|
|
22
|
+
The `PoolOptions` are passed to each of the `Pool` instances being created.
|
|
22
23
|
## Instance Properties
|
|
23
24
|
|
|
24
25
|
### `BalancedPool.upstreams`
|
|
@@ -33,6 +34,10 @@ Implements [Client.closed](Client.md#clientclosed)
|
|
|
33
34
|
|
|
34
35
|
Implements [Client.destroyed](Client.md#clientdestroyed)
|
|
35
36
|
|
|
37
|
+
### `Pool.stats`
|
|
38
|
+
|
|
39
|
+
Returns [`PoolStats`](PoolStats.md) instance for this pool.
|
|
40
|
+
|
|
36
41
|
## Instance Methods
|
|
37
42
|
|
|
38
43
|
### `BalancedPool.addUpstream(upstream)`
|
package/docs/api/Dispatcher.md
CHANGED
|
@@ -307,6 +307,59 @@ client.dispatch({
|
|
|
307
307
|
})
|
|
308
308
|
```
|
|
309
309
|
|
|
310
|
+
#### Example 3 - Dispatch POST request
|
|
311
|
+
|
|
312
|
+
```js
|
|
313
|
+
import { createServer } from 'http'
|
|
314
|
+
import { Client } from 'undici'
|
|
315
|
+
import { once } from 'events'
|
|
316
|
+
|
|
317
|
+
const server = createServer((request, response) => {
|
|
318
|
+
request.on('data', (data) => {
|
|
319
|
+
console.log(`Request Data: ${data.toString('utf8')}`)
|
|
320
|
+
const body = JSON.parse(data)
|
|
321
|
+
body.message = 'World'
|
|
322
|
+
response.end(JSON.stringify(body))
|
|
323
|
+
})
|
|
324
|
+
}).listen()
|
|
325
|
+
|
|
326
|
+
await once(server, 'listening')
|
|
327
|
+
|
|
328
|
+
const client = new Client(`http://localhost:${server.address().port}`)
|
|
329
|
+
|
|
330
|
+
const data = []
|
|
331
|
+
|
|
332
|
+
client.dispatch({
|
|
333
|
+
path: '/',
|
|
334
|
+
method: 'POST',
|
|
335
|
+
headers: {
|
|
336
|
+
'content-type': 'application/json'
|
|
337
|
+
},
|
|
338
|
+
body: JSON.stringify({ message: 'Hello' })
|
|
339
|
+
}, {
|
|
340
|
+
onConnect: () => {
|
|
341
|
+
console.log('Connected!')
|
|
342
|
+
},
|
|
343
|
+
onError: (error) => {
|
|
344
|
+
console.error(error)
|
|
345
|
+
},
|
|
346
|
+
onHeaders: (statusCode, headers) => {
|
|
347
|
+
console.log(`onHeaders | statusCode: ${statusCode} | headers: ${headers}`)
|
|
348
|
+
},
|
|
349
|
+
onData: (chunk) => {
|
|
350
|
+
console.log('onData: chunk received')
|
|
351
|
+
data.push(chunk)
|
|
352
|
+
},
|
|
353
|
+
onComplete: (trailers) => {
|
|
354
|
+
console.log(`onComplete | trailers: ${trailers}`)
|
|
355
|
+
const res = Buffer.concat(data).toString('utf8')
|
|
356
|
+
console.log(`Response Data: ${res}`)
|
|
357
|
+
client.close()
|
|
358
|
+
server.close()
|
|
359
|
+
}
|
|
360
|
+
})
|
|
361
|
+
```
|
|
362
|
+
|
|
310
363
|
### `Dispatcher.pipeline(options, handler)`
|
|
311
364
|
|
|
312
365
|
For easy use with [stream.pipeline](https://nodejs.org/api/stream.html#stream_stream_pipeline_source_transforms_destination_callback). The `handler` argument should return a `Readable` from which the result will be read. Usually it should just return the `body` argument unless some kind of transformation needs to be performed based on e.g. `headers` or `statusCode`. The `handler` should validate the response and save any required state. If there is an error, it should be thrown. The function returns a `Duplex` which writes to the request and reads from the response.
|
|
@@ -436,7 +489,7 @@ The `RequestOptions.method` property should not be value `'CONNECT'`.
|
|
|
436
489
|
- `body`
|
|
437
490
|
- `bodyUsed`
|
|
438
491
|
|
|
439
|
-
`body` contains the following additional
|
|
492
|
+
`body` contains the following additional extensions:
|
|
440
493
|
|
|
441
494
|
- `dump({ limit: Integer })`, dump the response by reading up to `limit` bytes without killing the socket (optional) - Default: 262144.
|
|
442
495
|
|
package/docs/api/MockPool.md
CHANGED
|
@@ -62,7 +62,7 @@ Returns: `MockInterceptor` corresponding to the input options.
|
|
|
62
62
|
|
|
63
63
|
We can define the behaviour of an intercepted request with the following options.
|
|
64
64
|
|
|
65
|
-
* **reply** `(statusCode: number, replyData: string | Buffer | object, responseOptions?: MockResponseOptions) => MockScope` - define a reply for a matching request. Default for `responseOptions` is `{}`.
|
|
65
|
+
* **reply** `(statusCode: number, replyData: string | Buffer | object | MockInterceptor.MockResponseDataHandler, responseOptions?: MockResponseOptions) => MockScope` - define a reply for a matching request. You can define this as a callback to read incoming request data. Default for `responseOptions` is `{}`.
|
|
66
66
|
* **replyWithError** `(error: Error) => MockScope` - define an error for a matching request to throw.
|
|
67
67
|
* **defaultReplyHeaders** `(headers: Record<string, string>) => MockInterceptor` - define default headers to be included in subsequent replies. These are in addition to headers on a specific reply.
|
|
68
68
|
* **defaultReplyTrailers** `(trailers: Record<string, string>) => MockInterceptor` - define default trailers to be included in subsequent replies. These are in addition to trailers on a specific reply.
|
|
@@ -113,6 +113,72 @@ for await (const data of body) {
|
|
|
113
113
|
}
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
+
#### Example - Mocked request using reply data callbacks
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
import { MockAgent, setGlobalDispatcher, request } from 'undici'
|
|
120
|
+
|
|
121
|
+
const mockAgent = new MockAgent()
|
|
122
|
+
setGlobalDispatcher(mockAgent)
|
|
123
|
+
|
|
124
|
+
const mockPool = mockAgent.get('http://localhost:3000')
|
|
125
|
+
|
|
126
|
+
mockPool.intercept({
|
|
127
|
+
path: '/echo',
|
|
128
|
+
method: 'GET',
|
|
129
|
+
headers: {
|
|
130
|
+
'User-Agent': 'undici',
|
|
131
|
+
Host: 'example.com'
|
|
132
|
+
}
|
|
133
|
+
}).reply(200, ({ headers }) => ({ message: headers.get('message') }))
|
|
134
|
+
|
|
135
|
+
const { statusCode, body, headers } = await request('http://localhost:3000', {
|
|
136
|
+
headers: {
|
|
137
|
+
message: 'hello world!'
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
console.log('response received', statusCode) // response received 200
|
|
142
|
+
console.log('headers', headers) // { 'content-type': 'application/json' }
|
|
143
|
+
|
|
144
|
+
for await (const data of body) {
|
|
145
|
+
console.log('data', data.toString('utf8')) // { "message":"hello world!" }
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### Example - Mocked request using reply options callback
|
|
150
|
+
|
|
151
|
+
```js
|
|
152
|
+
import { MockAgent, setGlobalDispatcher, request } from 'undici'
|
|
153
|
+
|
|
154
|
+
const mockAgent = new MockAgent()
|
|
155
|
+
setGlobalDispatcher(mockAgent)
|
|
156
|
+
|
|
157
|
+
const mockPool = mockAgent.get('http://localhost:3000')
|
|
158
|
+
|
|
159
|
+
mockPool.intercept({
|
|
160
|
+
path: '/echo',
|
|
161
|
+
method: 'GET',
|
|
162
|
+
headers: {
|
|
163
|
+
'User-Agent': 'undici',
|
|
164
|
+
Host: 'example.com'
|
|
165
|
+
}
|
|
166
|
+
}).reply(({ headers }) => ({ statusCode: 200, data: { message: headers.get('message') }})))
|
|
167
|
+
|
|
168
|
+
const { statusCode, body, headers } = await request('http://localhost:3000', {
|
|
169
|
+
headers: {
|
|
170
|
+
message: 'hello world!'
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
console.log('response received', statusCode) // response received 200
|
|
175
|
+
console.log('headers', headers) // { 'content-type': 'application/json' }
|
|
176
|
+
|
|
177
|
+
for await (const data of body) {
|
|
178
|
+
console.log('data', data.toString('utf8')) // { "message":"hello world!" }
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
116
182
|
#### Example - Basic Mocked requests with multiple intercepts
|
|
117
183
|
|
|
118
184
|
```js
|
|
@@ -130,7 +196,7 @@ mockPool.intercept({
|
|
|
130
196
|
|
|
131
197
|
mockPool.intercept({
|
|
132
198
|
path: '/hello',
|
|
133
|
-
method: 'GET'
|
|
199
|
+
method: 'GET',
|
|
134
200
|
}).reply(200, 'hello')
|
|
135
201
|
|
|
136
202
|
const result1 = await request('http://localhost:3000/foo')
|
package/docs/api/Pool.md
CHANGED
|
@@ -30,6 +30,10 @@ Implements [Client.closed](Client.md#clientclosed)
|
|
|
30
30
|
|
|
31
31
|
Implements [Client.destroyed](Client.md#clientdestroyed)
|
|
32
32
|
|
|
33
|
+
### `Pool.stats`
|
|
34
|
+
|
|
35
|
+
Returns [`PoolStats`](PoolStats.md) instance for this pool.
|
|
36
|
+
|
|
33
37
|
## Instance Methods
|
|
34
38
|
|
|
35
39
|
### `Pool.close([callback])`
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Class: PoolStats
|
|
2
|
+
|
|
3
|
+
Aggregate stats for a [Pool](Pool.md) or [BalancedPool](BalancedPool.md).
|
|
4
|
+
|
|
5
|
+
## `new PoolStats(pool)`
|
|
6
|
+
|
|
7
|
+
Arguments:
|
|
8
|
+
|
|
9
|
+
* **pool** `Pool` - Pool or BalancedPool from which to return stats.
|
|
10
|
+
|
|
11
|
+
## Instance Properties
|
|
12
|
+
|
|
13
|
+
### `PoolStats.connected`
|
|
14
|
+
|
|
15
|
+
Number of open socket connections in this pool.
|
|
16
|
+
|
|
17
|
+
### `PoolStats.free`
|
|
18
|
+
|
|
19
|
+
Number of open socket connections in this pool that do not have an active request.
|
|
20
|
+
|
|
21
|
+
### `PoolStats.pending`
|
|
22
|
+
|
|
23
|
+
Number of pending requests across all clients in this pool.
|
|
24
|
+
|
|
25
|
+
### `PoolStats.queued`
|
|
26
|
+
|
|
27
|
+
Number of queued requests across all clients in this pool.
|
|
28
|
+
|
|
29
|
+
### `PoolStats.running`
|
|
30
|
+
|
|
31
|
+
Number of currently active requests across all clients in this pool.
|
|
32
|
+
|
|
33
|
+
### `PoolStats.size`
|
|
34
|
+
|
|
35
|
+
Number of active, pending, or queued requests across all clients in this pool.
|
package/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ import MockClient = require('./types/mock-client')
|
|
|
10
10
|
import MockPool = require('./types/mock-pool')
|
|
11
11
|
import MockAgent = require('./types/mock-agent')
|
|
12
12
|
import mockErrors = require('./types/mock-errors')
|
|
13
|
-
import ProxyAgent
|
|
13
|
+
import ProxyAgent = require('./types/proxy-agent')
|
|
14
14
|
import { request, pipeline, stream, connect, upgrade } from './types/api'
|
|
15
15
|
|
|
16
16
|
export * from './types/fetch'
|
package/index.js
CHANGED
|
@@ -94,12 +94,12 @@ module.exports.getGlobalDispatcher = getGlobalDispatcher
|
|
|
94
94
|
|
|
95
95
|
if (nodeMajor > 16 || (nodeMajor === 16 && nodeMinor >= 5)) {
|
|
96
96
|
let fetchImpl = null
|
|
97
|
-
module.exports.fetch = async function fetch (resource
|
|
97
|
+
module.exports.fetch = async function fetch (resource) {
|
|
98
98
|
if (!fetchImpl) {
|
|
99
99
|
fetchImpl = require('./lib/fetch')
|
|
100
100
|
}
|
|
101
101
|
const dispatcher = getGlobalDispatcher()
|
|
102
|
-
return fetchImpl.
|
|
102
|
+
return fetchImpl.apply(dispatcher, arguments)
|
|
103
103
|
}
|
|
104
104
|
module.exports.Headers = require('./lib/fetch/headers').Headers
|
|
105
105
|
module.exports.Response = require('./lib/fetch/response').Response
|
package/lib/api/api-connect.js
CHANGED
|
@@ -15,7 +15,7 @@ class ConnectHandler extends AsyncResource {
|
|
|
15
15
|
throw new InvalidArgumentError('invalid callback')
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const { signal, opaque } = opts
|
|
18
|
+
const { signal, opaque, responseHeaders } = opts
|
|
19
19
|
|
|
20
20
|
if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') {
|
|
21
21
|
throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget')
|
|
@@ -24,6 +24,7 @@ class ConnectHandler extends AsyncResource {
|
|
|
24
24
|
super('UNDICI_CONNECT')
|
|
25
25
|
|
|
26
26
|
this.opaque = opaque || null
|
|
27
|
+
this.responseHeaders = responseHeaders || null
|
|
27
28
|
this.callback = callback
|
|
28
29
|
this.abort = null
|
|
29
30
|
|
|
@@ -43,15 +44,16 @@ class ConnectHandler extends AsyncResource {
|
|
|
43
44
|
throw new SocketError('bad connect', null)
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
onUpgrade (statusCode,
|
|
47
|
+
onUpgrade (statusCode, rawHeaders, socket) {
|
|
47
48
|
const { callback, opaque, context } = this
|
|
48
49
|
|
|
49
50
|
removeSignal(this)
|
|
50
51
|
|
|
51
52
|
this.callback = null
|
|
53
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
52
54
|
this.runInAsyncScope(callback, null, null, {
|
|
53
55
|
statusCode,
|
|
54
|
-
headers
|
|
56
|
+
headers,
|
|
55
57
|
socket,
|
|
56
58
|
opaque,
|
|
57
59
|
context
|
package/lib/api/api-pipeline.js
CHANGED
|
@@ -69,7 +69,7 @@ class PipelineHandler extends AsyncResource {
|
|
|
69
69
|
throw new InvalidArgumentError('invalid handler')
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
const { signal, method, opaque, onInfo } = opts
|
|
72
|
+
const { signal, method, opaque, onInfo, responseHeaders } = opts
|
|
73
73
|
|
|
74
74
|
if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') {
|
|
75
75
|
throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget')
|
|
@@ -86,6 +86,7 @@ class PipelineHandler extends AsyncResource {
|
|
|
86
86
|
super('UNDICI_PIPELINE')
|
|
87
87
|
|
|
88
88
|
this.opaque = opaque || null
|
|
89
|
+
this.responseHeaders = responseHeaders || null
|
|
89
90
|
this.handler = handler
|
|
90
91
|
this.abort = null
|
|
91
92
|
this.context = null
|
|
@@ -156,12 +157,13 @@ class PipelineHandler extends AsyncResource {
|
|
|
156
157
|
this.context = context
|
|
157
158
|
}
|
|
158
159
|
|
|
159
|
-
onHeaders (statusCode,
|
|
160
|
+
onHeaders (statusCode, rawHeaders, resume) {
|
|
160
161
|
const { opaque, handler, context } = this
|
|
161
162
|
|
|
162
163
|
if (statusCode < 200) {
|
|
163
164
|
if (this.onInfo) {
|
|
164
|
-
this.
|
|
165
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
166
|
+
this.onInfo({ statusCode, headers })
|
|
165
167
|
}
|
|
166
168
|
return
|
|
167
169
|
}
|
|
@@ -171,9 +173,10 @@ class PipelineHandler extends AsyncResource {
|
|
|
171
173
|
let body
|
|
172
174
|
try {
|
|
173
175
|
this.handler = null
|
|
176
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
174
177
|
body = this.runInAsyncScope(handler, null, {
|
|
175
178
|
statusCode,
|
|
176
|
-
headers
|
|
179
|
+
headers,
|
|
177
180
|
opaque,
|
|
178
181
|
body: this.res,
|
|
179
182
|
context
|
package/lib/api/api-request.js
CHANGED
|
@@ -15,7 +15,7 @@ class RequestHandler extends AsyncResource {
|
|
|
15
15
|
throw new InvalidArgumentError('invalid opts')
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const { signal, method, opaque, body, onInfo } = opts
|
|
18
|
+
const { signal, method, opaque, body, onInfo, responseHeaders } = opts
|
|
19
19
|
|
|
20
20
|
try {
|
|
21
21
|
if (typeof callback !== 'function') {
|
|
@@ -42,6 +42,7 @@ class RequestHandler extends AsyncResource {
|
|
|
42
42
|
throw err
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
this.responseHeaders = responseHeaders || null
|
|
45
46
|
this.opaque = opaque || null
|
|
46
47
|
this.callback = callback
|
|
47
48
|
this.res = null
|
|
@@ -69,25 +70,27 @@ class RequestHandler extends AsyncResource {
|
|
|
69
70
|
this.context = context
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
onHeaders (statusCode,
|
|
73
|
+
onHeaders (statusCode, rawHeaders, resume) {
|
|
73
74
|
const { callback, opaque, abort, context } = this
|
|
74
75
|
|
|
75
76
|
if (statusCode < 200) {
|
|
76
77
|
if (this.onInfo) {
|
|
77
|
-
this.
|
|
78
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
79
|
+
this.onInfo({ statusCode, headers })
|
|
78
80
|
}
|
|
79
81
|
return
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
const parsedHeaders = util.parseHeaders(
|
|
84
|
+
const parsedHeaders = util.parseHeaders(rawHeaders)
|
|
83
85
|
const body = new Readable(resume, abort, parsedHeaders['content-type'])
|
|
84
86
|
|
|
85
87
|
this.callback = null
|
|
86
88
|
this.res = body
|
|
89
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
87
90
|
|
|
88
91
|
this.runInAsyncScope(callback, null, null, {
|
|
89
92
|
statusCode,
|
|
90
|
-
headers
|
|
93
|
+
headers,
|
|
91
94
|
trailers: this.trailers,
|
|
92
95
|
opaque,
|
|
93
96
|
body,
|
package/lib/api/api-stream.js
CHANGED
|
@@ -16,7 +16,7 @@ class StreamHandler extends AsyncResource {
|
|
|
16
16
|
throw new InvalidArgumentError('invalid opts')
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const { signal, method, opaque, body, onInfo } = opts
|
|
19
|
+
const { signal, method, opaque, body, onInfo, responseHeaders } = opts
|
|
20
20
|
|
|
21
21
|
try {
|
|
22
22
|
if (typeof callback !== 'function') {
|
|
@@ -47,6 +47,7 @@ class StreamHandler extends AsyncResource {
|
|
|
47
47
|
throw err
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
this.responseHeaders = responseHeaders || null
|
|
50
51
|
this.opaque = opaque || null
|
|
51
52
|
this.factory = factory
|
|
52
53
|
this.callback = callback
|
|
@@ -75,20 +76,22 @@ class StreamHandler extends AsyncResource {
|
|
|
75
76
|
this.context = context
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
onHeaders (statusCode,
|
|
79
|
+
onHeaders (statusCode, rawHeaders, resume) {
|
|
79
80
|
const { factory, opaque, context } = this
|
|
80
81
|
|
|
81
82
|
if (statusCode < 200) {
|
|
82
83
|
if (this.onInfo) {
|
|
83
|
-
this.
|
|
84
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
85
|
+
this.onInfo({ statusCode, headers })
|
|
84
86
|
}
|
|
85
87
|
return
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
this.factory = null
|
|
91
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
89
92
|
const res = this.runInAsyncScope(factory, null, {
|
|
90
93
|
statusCode,
|
|
91
|
-
headers
|
|
94
|
+
headers,
|
|
92
95
|
opaque,
|
|
93
96
|
context
|
|
94
97
|
})
|
package/lib/api/api-upgrade.js
CHANGED
|
@@ -16,7 +16,7 @@ class UpgradeHandler extends AsyncResource {
|
|
|
16
16
|
throw new InvalidArgumentError('invalid callback')
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
const { signal, opaque } = opts
|
|
19
|
+
const { signal, opaque, responseHeaders } = opts
|
|
20
20
|
|
|
21
21
|
if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') {
|
|
22
22
|
throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget')
|
|
@@ -24,6 +24,7 @@ class UpgradeHandler extends AsyncResource {
|
|
|
24
24
|
|
|
25
25
|
super('UNDICI_UPGRADE')
|
|
26
26
|
|
|
27
|
+
this.responseHeaders = responseHeaders || null
|
|
27
28
|
this.opaque = opaque || null
|
|
28
29
|
this.callback = callback
|
|
29
30
|
this.abort = null
|
|
@@ -45,7 +46,7 @@ class UpgradeHandler extends AsyncResource {
|
|
|
45
46
|
throw new SocketError('bad upgrade', null)
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
onUpgrade (statusCode,
|
|
49
|
+
onUpgrade (statusCode, rawHeaders, socket) {
|
|
49
50
|
const { callback, opaque, context } = this
|
|
50
51
|
|
|
51
52
|
assert.strictEqual(statusCode, 101)
|
|
@@ -53,8 +54,9 @@ class UpgradeHandler extends AsyncResource {
|
|
|
53
54
|
removeSignal(this)
|
|
54
55
|
|
|
55
56
|
this.callback = null
|
|
57
|
+
const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders)
|
|
56
58
|
this.runInAsyncScope(callback, null, null, {
|
|
57
|
-
headers
|
|
59
|
+
headers,
|
|
58
60
|
socket,
|
|
59
61
|
opaque,
|
|
60
62
|
context
|
package/lib/balanced-pool.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
BalancedPoolMissingUpstreamError
|
|
4
|
+
BalancedPoolMissingUpstreamError,
|
|
5
|
+
InvalidArgumentError
|
|
5
6
|
} = require('./core/errors')
|
|
6
7
|
const {
|
|
7
8
|
PoolBase,
|
|
@@ -13,11 +14,17 @@ const {
|
|
|
13
14
|
} = require('./pool-base')
|
|
14
15
|
const Pool = require('./pool')
|
|
15
16
|
const { kUrl } = require('./core/symbols')
|
|
17
|
+
const { parseOrigin } = require('./core/util')
|
|
18
|
+
const kFactory = Symbol('factory')
|
|
16
19
|
|
|
17
20
|
const kOptions = Symbol('options')
|
|
18
21
|
|
|
22
|
+
function defaultFactory (origin, opts) {
|
|
23
|
+
return new Pool(origin, opts);
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
class BalancedPool extends PoolBase {
|
|
20
|
-
constructor (upstreams = [], opts = {}) {
|
|
27
|
+
constructor (upstreams = [], { factory = defaultFactory, ...opts } = {}) {
|
|
21
28
|
super()
|
|
22
29
|
|
|
23
30
|
this[kOptions] = opts
|
|
@@ -26,28 +33,38 @@ class BalancedPool extends PoolBase {
|
|
|
26
33
|
upstreams = [upstreams]
|
|
27
34
|
}
|
|
28
35
|
|
|
36
|
+
if (typeof factory !== 'function') {
|
|
37
|
+
throw new InvalidArgumentError('factory must be a function.')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this[kFactory] = factory
|
|
41
|
+
|
|
29
42
|
for (const upstream of upstreams) {
|
|
30
43
|
this.addUpstream(upstream)
|
|
31
44
|
}
|
|
32
45
|
}
|
|
33
46
|
|
|
34
47
|
addUpstream (upstream) {
|
|
48
|
+
const upstreamOrigin = parseOrigin(upstream).origin
|
|
49
|
+
|
|
35
50
|
if (this[kClients].find((pool) => (
|
|
36
|
-
pool[kUrl].origin ===
|
|
51
|
+
pool[kUrl].origin === upstreamOrigin &&
|
|
37
52
|
pool.closed !== true &&
|
|
38
53
|
pool.destroyed !== true
|
|
39
54
|
))) {
|
|
40
55
|
return this
|
|
41
56
|
}
|
|
42
57
|
|
|
43
|
-
this[kAddClient](
|
|
58
|
+
this[kAddClient](this[kFactory](upstreamOrigin, Object.assign({}, this[kOptions])))
|
|
44
59
|
|
|
45
60
|
return this
|
|
46
61
|
}
|
|
47
62
|
|
|
48
63
|
removeUpstream (upstream) {
|
|
64
|
+
const upstreamOrigin = parseOrigin(upstream).origin
|
|
65
|
+
|
|
49
66
|
const pool = this[kClients].find((pool) => (
|
|
50
|
-
pool[kUrl].origin ===
|
|
67
|
+
pool[kUrl].origin === upstreamOrigin &&
|
|
51
68
|
pool.closed !== true &&
|
|
52
69
|
pool.destroyed !== true
|
|
53
70
|
))
|
package/lib/client.js
CHANGED
|
@@ -24,9 +24,6 @@ const {
|
|
|
24
24
|
HTTPParserError
|
|
25
25
|
} = require('./core/errors')
|
|
26
26
|
const buildConnector = require('./core/connect')
|
|
27
|
-
const llhttpWasmData = require('./llhttp/llhttp.wasm.js')
|
|
28
|
-
const llhttpSimdWasmData = require('./llhttp/llhttp_simd.wasm.js')
|
|
29
|
-
|
|
30
27
|
const {
|
|
31
28
|
kUrl,
|
|
32
29
|
kReset,
|
|
@@ -411,9 +408,11 @@ const constants = require('./llhttp/constants')
|
|
|
411
408
|
const EMPTY_BUF = Buffer.alloc(0)
|
|
412
409
|
|
|
413
410
|
async function lazyllhttp () {
|
|
411
|
+
const llhttpWasmData = process.env.JEST_WORKER_ID ? require('./llhttp/llhttp.wasm.js') : undefined
|
|
412
|
+
|
|
414
413
|
let mod
|
|
415
414
|
try {
|
|
416
|
-
mod = await WebAssembly.compile(Buffer.from(
|
|
415
|
+
mod = await WebAssembly.compile(Buffer.from(require('./llhttp/llhttp_simd.wasm.js'), 'base64'))
|
|
417
416
|
} catch (e) {
|
|
418
417
|
/* istanbul ignore next */
|
|
419
418
|
|
|
@@ -421,7 +420,7 @@ async function lazyllhttp () {
|
|
|
421
420
|
// being enabled, but the occurring of this other error
|
|
422
421
|
// * https://github.com/emscripten-core/emscripten/issues/11495
|
|
423
422
|
// got me to remove that check to avoid breaking Node 12.
|
|
424
|
-
mod = await WebAssembly.compile(Buffer.from(
|
|
423
|
+
mod = await WebAssembly.compile(Buffer.from(llhttpWasmData || require('./llhttp/llhttp.wasm.js'), 'base64'))
|
|
425
424
|
}
|
|
426
425
|
|
|
427
426
|
return await WebAssembly.instantiate(mod, {
|
|
@@ -1484,7 +1483,7 @@ function write (client, request) {
|
|
|
1484
1483
|
|
|
1485
1484
|
let header = `${method} ${path} HTTP/1.1\r\n`
|
|
1486
1485
|
|
|
1487
|
-
if (host) {
|
|
1486
|
+
if (typeof host === 'string') {
|
|
1488
1487
|
header += `host: ${host}\r\n`
|
|
1489
1488
|
} else {
|
|
1490
1489
|
header += client[kHostHeader]
|
package/lib/core/connect.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const net = require('net')
|
|
4
|
-
const tls = require('tls')
|
|
5
4
|
const assert = require('assert')
|
|
6
5
|
const util = require('./util')
|
|
7
6
|
const { InvalidArgumentError, ConnectTimeoutError } = require('./errors')
|
|
7
|
+
let tls // include tls conditionally since it is not always available
|
|
8
8
|
|
|
9
9
|
// TODO: session re-use does not wait for the first
|
|
10
10
|
// connection to resolve the session and might therefore
|
|
@@ -24,6 +24,9 @@ function buildConnector ({ maxCachedSessions, socketPath, timeout, ...opts }) {
|
|
|
24
24
|
return function connect ({ hostname, host, protocol, port, servername }, callback) {
|
|
25
25
|
let socket
|
|
26
26
|
if (protocol === 'https:') {
|
|
27
|
+
if (!tls) {
|
|
28
|
+
tls = require('tls')
|
|
29
|
+
}
|
|
27
30
|
servername = servername || options.servername || util.getServerName(host) || null
|
|
28
31
|
|
|
29
32
|
const sessionKey = servername || hostname
|