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 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. We plan to ship breaking changes to this feature until it is out of experimental.
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>
@@ -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** `PoolOptions` (optional)
14
+ * **options** `BalancedPoolOptions` (optional)
15
15
 
16
- ### Parameter: `PoolOptions`
16
+ ### Parameter: `BalancedPoolOptions`
17
17
 
18
- The `PoolOptions` are passed to each of the `Pool` instances being created.
18
+ Extends: [`PoolOptions`](Pool.md#parameter-pooloptions)
19
19
 
20
- See: [`PoolOptions`](Pool.md#parameter-pooloptions)
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)`
@@ -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 extentions:
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
 
@@ -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 from './types/proxy-agent'
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, init) {
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.call(dispatcher, resource, init)
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
@@ -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, headers, socket) {
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: util.parseHeaders(headers),
56
+ headers,
55
57
  socket,
56
58
  opaque,
57
59
  context
@@ -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, headers, resume) {
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.onInfo({ statusCode, headers: util.parseHeaders(headers) })
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: util.parseHeaders(headers),
179
+ headers,
177
180
  opaque,
178
181
  body: this.res,
179
182
  context
@@ -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, headers, resume) {
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.onInfo({ statusCode, headers: util.parseHeaders(headers) })
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(headers)
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: parsedHeaders,
93
+ headers,
91
94
  trailers: this.trailers,
92
95
  opaque,
93
96
  body,
@@ -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, headers, resume) {
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.onInfo({ statusCode, headers: util.parseHeaders(headers) })
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: util.parseHeaders(headers),
94
+ headers,
92
95
  opaque,
93
96
  context
94
97
  })
@@ -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, headers, socket) {
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: util.parseHeaders(headers),
59
+ headers,
58
60
  socket,
59
61
  opaque,
60
62
  context
@@ -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 === upstream &&
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](new Pool(upstream, Object.assign({}, this[kOptions])))
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 === upstream &&
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(llhttpWasmData, 'base64'))
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(llhttpSimdWasmData, 'base64'))
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]
@@ -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