undici 4.8.1 → 4.8.2

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.
@@ -0,0 +1,100 @@
1
+ # Class: ProxyAgent
2
+
3
+ Extends: `undici.Dispatcher`
4
+
5
+ A Proxy Agent class that implements the Agent API. It allows the connection through proxy in a simple way.
6
+
7
+ ## `new ProxyAgent([options])`
8
+
9
+ Arguments:
10
+
11
+ * **options** `ProxyAgentOptions` (required) - It extends the `Agent` options.
12
+
13
+ Returns: `ProxyAgent`
14
+
15
+ ### Parameter: `ProxyAgentOptions`
16
+
17
+ Extends: [`AgentOptions`](docs/api/Agent.md#parameter-agentoptions)
18
+
19
+ * **uri** `string` (required) - It can be passed either by a string or a object containing `uri` as string.
20
+
21
+ Examples:
22
+
23
+ ```js
24
+ import { ProxyAgent } from 'undici'
25
+
26
+ const proxyAgent = new ProxyAgent('my.proxy.server')
27
+ // or
28
+ const proxyAgent = new ProxyAgent({ uri: 'my.proxy.server' })
29
+ ```
30
+
31
+ #### Example - Basic ProxyAgent instantiation
32
+
33
+ This will instantiate the ProxyAgent. It will not do anything until registered as the agent to use with requests.
34
+
35
+ ```js
36
+ import { ProxyAgent } from 'undici'
37
+
38
+ const proxyAgent = new ProxyAgent('my.proxy.server')
39
+ ```
40
+
41
+ #### Example - Basic Proxy Request with global agent dispatcher
42
+
43
+ ```js
44
+ import { setGlobalDispatcher, request, ProxyAgent } from 'undici'
45
+
46
+ const proxyAgent = new ProxyAgent('my.proxy.server')
47
+ setGlobalDispatcher(proxyAgent)
48
+
49
+ const { statusCode, body } = await request('http://localhost:3000/foo')
50
+
51
+ console.log('response received', statusCode) // response received 200
52
+
53
+ for await (const data of body) {
54
+ console.log('data', data.toString('utf8')) // data foo
55
+ }
56
+ ```
57
+
58
+ #### Example - Basic Proxy Request with local agent dispatcher
59
+
60
+ ```js
61
+ import { ProxyAgent, request } from 'undici'
62
+
63
+ const proxyAgent = new ProxyAgent('my.proxy.server')
64
+
65
+ const {
66
+ statusCode,
67
+ body
68
+ } = await request('http://localhost:3000/foo', { dispatcher: proxyAgent })
69
+
70
+ console.log('response received', statusCode) // response received 200
71
+
72
+ for await (const data of body) {
73
+ console.log('data', data.toString('utf8')) // data foo
74
+ }
75
+ ```
76
+
77
+ ### `ProxyAgent.close()`
78
+
79
+ Closes the proxy agent and waits for registered pools and clients to also close before resolving.
80
+
81
+ Returns: `Promise<void>`
82
+
83
+ #### Example - clean up after tests are complete
84
+
85
+ ```js
86
+ import { ProxyAgent, setGlobalDispatcher } from 'undici'
87
+
88
+ const proxyAgent = new ProxyAgent('my.proxy.server')
89
+ setGlobalDispatcher(proxyAgent)
90
+
91
+ await proxyAgent.close()
92
+ ```
93
+
94
+ ### `ProxyAgent.dispatch(options, handlers)`
95
+
96
+ Implements [`Agent.dispatch(options, handlers)`](docs/api/Agent.md#parameter-agentdispatchoptions).
97
+
98
+ ### `ProxyAgent.request(options[, callback])`
99
+
100
+ See [`Dispatcher.request(options [, callback])`](docs/api/Dispatcher.md#clientrequestoptions--callback).
@@ -1,6 +1,9 @@
1
1
  # Connecting through a proxy
2
2
 
3
- Connecting through a proxy is possible by properly configuring the `Client` or `Pool` constructor and request.
3
+ Connecting through a proxy is possible by:
4
+
5
+ - Using [AgentProxy](docs/api/ProxyAgent.md).
6
+ - Configuring `Client` or `Pool` constructor.
4
7
 
5
8
  The proxy url should be passed to the `Client` or `Pool` constructor, while the upstream server url
6
9
  should be added to every request call in the `path`.
package/index.d.ts CHANGED
@@ -10,13 +10,14 @@ 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
14
  import { request, pipeline, stream, connect, upgrade } from './types/api'
14
15
 
15
16
  export * from './types/fetch'
16
17
  export * from './types/file'
17
18
  export * from './types/formdata'
18
19
 
19
- export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, MockClient, MockPool, MockAgent, mockErrors }
20
+ export { Dispatcher, BalancedPool, Pool, Client, buildConnector, errors, Agent, request, stream, pipeline, connect, upgrade, setGlobalDispatcher, getGlobalDispatcher, MockClient, MockPool, MockAgent, mockErrors, ProxyAgent }
20
21
  export default Undici
21
22
 
22
23
  declare function Undici(url: string, opts: Pool.Options): Pool
package/index.js CHANGED
@@ -14,6 +14,7 @@ const MockClient = require('./lib/mock/mock-client')
14
14
  const MockAgent = require('./lib/mock/mock-agent')
15
15
  const MockPool = require('./lib/mock/mock-pool')
16
16
  const mockErrors = require('./lib/mock/mock-errors')
17
+ const ProxyAgent = require('./lib/proxy-agent')
17
18
 
18
19
  const nodeVersion = process.versions.node.split('.')
19
20
  const nodeMajor = Number(nodeVersion[0])
@@ -26,6 +27,7 @@ module.exports.Client = Client
26
27
  module.exports.Pool = Pool
27
28
  module.exports.BalancedPool = BalancedPool
28
29
  module.exports.Agent = Agent
30
+ module.exports.ProxyAgent = ProxyAgent
29
31
 
30
32
  module.exports.buildConnector = buildConnector
31
33
  module.exports.errors = errors
package/lib/client.js CHANGED
@@ -397,76 +397,77 @@ class Client extends Dispatcher {
397
397
  }
398
398
  }
399
399
 
400
- const { resolve } = require('path')
401
- const { readFileSync } = require('fs')
402
400
  const constants = require('./llhttp/constants')
403
401
  const EMPTY_BUF = Buffer.alloc(0)
404
402
 
403
+ let llhttpPromise
405
404
  let llhttpInstance
406
- function lazyllhttp () {
407
- if (!llhttpInstance) {
408
- let mod
409
- try {
410
- mod = new WebAssembly.Module(readFileSync(resolve(__dirname, './llhttp/llhttp_simd.wasm')))
411
- } catch (e) {
412
- /* istanbul ignore next */
405
+ async function lazyllhttp () {
406
+ const { resolve } = require('path')
407
+ const { readFile } = require('fs').promises
413
408
 
414
- // We could check if the error was caused by the simd option not
415
- // being enabled, but the occurring of this other error
416
- // * https://github.com/emscripten-core/emscripten/issues/11495
417
- // got me to remove that check to avoid breaking Node 12.
418
- mod = new WebAssembly.Module(readFileSync(resolve(__dirname, './llhttp/llhttp.wasm')))
419
- }
409
+ let mod
410
+ try {
411
+ mod = await WebAssembly.compile(await readFile(resolve(__dirname, './llhttp/llhttp_simd.wasm')))
412
+ } catch (e) {
413
+ /* istanbul ignore next */
420
414
 
421
- llhttpInstance = new WebAssembly.Instance(mod, {
422
- env: {
423
- /* eslint-disable camelcase */
415
+ // We could check if the error was caused by the simd option not
416
+ // being enabled, but the occurring of this other error
417
+ // * https://github.com/emscripten-core/emscripten/issues/11495
418
+ // got me to remove that check to avoid breaking Node 12.
419
+ mod = await WebAssembly.compile(await readFile(resolve(__dirname, './llhttp/llhttp.wasm')))
420
+ }
424
421
 
425
- wasm_on_url: (p, at, len) => {
426
- /* istanbul ignore next */
427
- return 0
428
- },
429
- wasm_on_status: (p, at, len) => {
430
- assert.strictEqual(currentParser.ptr, p)
431
- const start = at - currentBufferPtr
432
- const end = start + len
433
- return currentParser.onStatus(currentBufferRef.slice(start, end)) || 0
434
- },
435
- wasm_on_message_begin: (p) => {
436
- assert.strictEqual(currentParser.ptr, p)
437
- return currentParser.onMessageBegin() || 0
438
- },
439
- wasm_on_header_field: (p, at, len) => {
440
- assert.strictEqual(currentParser.ptr, p)
441
- const start = at - currentBufferPtr
442
- const end = start + len
443
- return currentParser.onHeaderField(currentBufferRef.slice(start, end)) || 0
444
- },
445
- wasm_on_header_value: (p, at, len) => {
446
- assert.strictEqual(currentParser.ptr, p)
447
- const start = at - currentBufferPtr
448
- const end = start + len
449
- return currentParser.onHeaderValue(currentBufferRef.slice(start, end)) || 0
450
- },
451
- wasm_on_headers_complete: (p, statusCode, upgrade, shouldKeepAlive) => {
452
- assert.strictEqual(currentParser.ptr, p)
453
- return currentParser.onHeadersComplete(statusCode, Boolean(upgrade), Boolean(shouldKeepAlive)) || 0
454
- },
455
- wasm_on_body: (p, at, len) => {
456
- assert.strictEqual(currentParser.ptr, p)
457
- const start = at - currentBufferPtr
458
- const end = start + len
459
- return currentParser.onBody(currentBufferRef.slice(start, end)) || 0
460
- },
461
- wasm_on_message_complete: (p) => {
462
- assert.strictEqual(currentParser.ptr, p)
463
- return currentParser.onMessageComplete() || 0
464
- }
422
+ llhttpInstance = new WebAssembly.Instance(mod, {
423
+ env: {
424
+ /* eslint-disable camelcase */
465
425
 
466
- /* eslint-enable camelcase */
426
+ wasm_on_url: (p, at, len) => {
427
+ /* istanbul ignore next */
428
+ return 0
429
+ },
430
+ wasm_on_status: (p, at, len) => {
431
+ assert.strictEqual(currentParser.ptr, p)
432
+ const start = at - currentBufferPtr
433
+ const end = start + len
434
+ return currentParser.onStatus(currentBufferRef.slice(start, end)) || 0
435
+ },
436
+ wasm_on_message_begin: (p) => {
437
+ assert.strictEqual(currentParser.ptr, p)
438
+ return currentParser.onMessageBegin() || 0
439
+ },
440
+ wasm_on_header_field: (p, at, len) => {
441
+ assert.strictEqual(currentParser.ptr, p)
442
+ const start = at - currentBufferPtr
443
+ const end = start + len
444
+ return currentParser.onHeaderField(currentBufferRef.slice(start, end)) || 0
445
+ },
446
+ wasm_on_header_value: (p, at, len) => {
447
+ assert.strictEqual(currentParser.ptr, p)
448
+ const start = at - currentBufferPtr
449
+ const end = start + len
450
+ return currentParser.onHeaderValue(currentBufferRef.slice(start, end)) || 0
451
+ },
452
+ wasm_on_headers_complete: (p, statusCode, upgrade, shouldKeepAlive) => {
453
+ assert.strictEqual(currentParser.ptr, p)
454
+ return currentParser.onHeadersComplete(statusCode, Boolean(upgrade), Boolean(shouldKeepAlive)) || 0
455
+ },
456
+ wasm_on_body: (p, at, len) => {
457
+ assert.strictEqual(currentParser.ptr, p)
458
+ const start = at - currentBufferPtr
459
+ const end = start + len
460
+ return currentParser.onBody(currentBufferRef.slice(start, end)) || 0
461
+ },
462
+ wasm_on_message_complete: (p) => {
463
+ assert.strictEqual(currentParser.ptr, p)
464
+ return currentParser.onMessageComplete() || 0
467
465
  }
468
- })
469
- }
466
+
467
+ /* eslint-enable camelcase */
468
+ }
469
+ })
470
+
470
471
  return llhttpInstance
471
472
  }
472
473
 
@@ -480,10 +481,10 @@ const TIMEOUT_BODY = 2
480
481
  const TIMEOUT_IDLE = 3
481
482
 
482
483
  class Parser {
483
- constructor (client, socket) {
484
+ constructor (client, socket, { exports }) {
484
485
  assert(Number.isFinite(client[kMaxHeadersSize]) && client[kMaxHeadersSize] > 0)
485
486
 
486
- this.llhttp = lazyllhttp().exports
487
+ this.llhttp = exports
487
488
  this.ptr = this.llhttp.llhttp_alloc(constants.TYPE.RESPONSE)
488
489
  this.client = client
489
490
  this.socket = socket
@@ -1096,7 +1097,7 @@ function onSocketClose () {
1096
1097
  resume(client)
1097
1098
  }
1098
1099
 
1099
- function connect (client) {
1100
+ async function connect (client) {
1100
1101
  assert(!client[kConnecting])
1101
1102
  assert(!client[kSocket])
1102
1103
 
@@ -1128,78 +1129,97 @@ function connect (client) {
1128
1129
  })
1129
1130
  }
1130
1131
 
1131
- client[kConnector]({
1132
- host,
1133
- hostname,
1134
- protocol,
1135
- port,
1136
- servername: client[kServerName]
1137
- }, function (err, socket) {
1138
- client[kConnecting] = false
1139
-
1140
- if (err) {
1141
- if (channels.connectError.hasSubscribers) {
1142
- channels.connectError.publish({
1143
- connectParams: {
1144
- host,
1145
- hostname,
1146
- protocol,
1147
- port,
1148
- servername: client[kServerName]
1149
- },
1150
- connector: client[kConnector],
1151
- error: err
1152
- })
1132
+ try {
1133
+ if (!llhttpInstance) {
1134
+ if (!llhttpPromise) {
1135
+ llhttpPromise = lazyllhttp()
1153
1136
  }
1137
+ await llhttpPromise
1138
+ assert(llhttpInstance)
1139
+ llhttpPromise = null
1140
+ }
1154
1141
 
1155
- if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
1156
- assert(client[kRunning] === 0)
1157
- while (client[kPending] > 0 && client[kQueue][client[kPendingIdx]].servername === client[kServerName]) {
1158
- const request = client[kQueue][client[kPendingIdx]++]
1159
- errorRequest(client, request, err)
1142
+ const socket = await new Promise((resolve, reject) => {
1143
+ client[kConnector]({
1144
+ host,
1145
+ hostname,
1146
+ protocol,
1147
+ port,
1148
+ servername: client[kServerName]
1149
+ }, (err, socket) => {
1150
+ if (err) {
1151
+ reject(err)
1152
+ } else {
1153
+ resolve(socket)
1160
1154
  }
1161
- } else {
1162
- onError(client, err)
1163
- }
1155
+ })
1156
+ })
1164
1157
 
1165
- client.emit('connectionError', client[kUrl], [client], err)
1166
- } else {
1167
- assert(socket)
1158
+ client[kConnecting] = false
1168
1159
 
1169
- client[kSocket] = socket
1160
+ assert(socket)
1170
1161
 
1171
- socket[kNoRef] = false
1172
- socket[kWriting] = false
1173
- socket[kReset] = false
1174
- socket[kBlocking] = false
1175
- socket[kError] = null
1176
- socket[kParser] = new Parser(client, socket)
1177
- socket[kClient] = client
1178
- socket
1179
- .on('error', onSocketError)
1180
- .on('readable', onSocketReadable)
1181
- .on('end', onSocketEnd)
1182
- .on('close', onSocketClose)
1183
-
1184
- if (channels.connected.hasSubscribers) {
1185
- channels.connected.publish({
1186
- connectParams: {
1187
- host,
1188
- hostname,
1189
- protocol,
1190
- port,
1191
- servername: client[kServerName]
1192
- },
1193
- connector: client[kConnector],
1194
- socket
1195
- })
1196
- }
1162
+ client[kSocket] = socket
1163
+
1164
+ socket[kNoRef] = false
1165
+ socket[kWriting] = false
1166
+ socket[kReset] = false
1167
+ socket[kBlocking] = false
1168
+ socket[kError] = null
1169
+ socket[kParser] = new Parser(client, socket, llhttpInstance)
1170
+ socket[kClient] = client
1171
+ socket
1172
+ .on('error', onSocketError)
1173
+ .on('readable', onSocketReadable)
1174
+ .on('end', onSocketEnd)
1175
+ .on('close', onSocketClose)
1176
+
1177
+ if (channels.connected.hasSubscribers) {
1178
+ channels.connected.publish({
1179
+ connectParams: {
1180
+ host,
1181
+ hostname,
1182
+ protocol,
1183
+ port,
1184
+ servername: client[kServerName]
1185
+ },
1186
+ connector: client[kConnector],
1187
+ socket
1188
+ })
1189
+ }
1197
1190
 
1198
- client.emit('connect', client[kUrl], [client])
1191
+ client.emit('connect', client[kUrl], [client])
1192
+ } catch (err) {
1193
+ client[kConnecting] = false
1194
+
1195
+ if (channels.connectError.hasSubscribers) {
1196
+ channels.connectError.publish({
1197
+ connectParams: {
1198
+ host,
1199
+ hostname,
1200
+ protocol,
1201
+ port,
1202
+ servername: client[kServerName]
1203
+ },
1204
+ connector: client[kConnector],
1205
+ error: err
1206
+ })
1199
1207
  }
1200
1208
 
1201
- resume(client)
1202
- })
1209
+ if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
1210
+ assert(client[kRunning] === 0)
1211
+ while (client[kPending] > 0 && client[kQueue][client[kPendingIdx]].servername === client[kServerName]) {
1212
+ const request = client[kQueue][client[kPendingIdx]++]
1213
+ errorRequest(client, request, err)
1214
+ }
1215
+ } else {
1216
+ onError(client, err)
1217
+ }
1218
+
1219
+ client.emit('connectionError', client[kUrl], [client], err)
1220
+ }
1221
+
1222
+ resume(client)
1203
1223
  }
1204
1224
 
1205
1225
  function emitDrain (client) {
@@ -40,5 +40,6 @@ module.exports = {
40
40
  kHostHeader: Symbol('host header'),
41
41
  kConnector: Symbol('connector'),
42
42
  kStrictContentLength: Symbol('strict content length'),
43
- kMaxRedirections: Symbol('maxRedirections')
43
+ kMaxRedirections: Symbol('maxRedirections'),
44
+ kProxy: Symbol('proxy agent options')
44
45
  }
@@ -0,0 +1,59 @@
1
+ 'use strict'
2
+
3
+ const { kClients, kProxy } = require('./core/symbols')
4
+ const url = require('url')
5
+ const Agent = require('./agent')
6
+ const Dispatcher = require('./dispatcher')
7
+ const { InvalidArgumentError } = require('./core/errors')
8
+
9
+ const kAgent = Symbol('proxy agent')
10
+
11
+ class ProxyAgent extends Dispatcher {
12
+ constructor (opts) {
13
+ super(opts)
14
+ this[kProxy] = buildProxyOptions(opts)
15
+
16
+ const agent = new Agent(opts)
17
+ this[kAgent] = agent
18
+
19
+ this[kClients] = agent[kClients]
20
+ }
21
+
22
+ dispatch (opts, handler) {
23
+ const { host } = url.parse(opts.origin)
24
+ return this[kAgent].dispatch(
25
+ {
26
+ ...opts,
27
+ origin: this[kProxy].uri,
28
+ path: opts.origin + opts.path,
29
+ headers: {
30
+ ...opts.headers,
31
+ host
32
+ },
33
+ },
34
+ handler
35
+ )
36
+ }
37
+
38
+ async close () {
39
+ await this[kAgent].close()
40
+ this[kClients].clear()
41
+ }
42
+ }
43
+
44
+ function buildProxyOptions(opts) {
45
+ if (typeof opts === 'string') {
46
+ opts = { uri: opts }
47
+ }
48
+
49
+ if (!opts || !opts.uri) {
50
+ throw new InvalidArgumentError('Proxy opts.uri is mandatory')
51
+ }
52
+
53
+ return {
54
+ uri: opts.uri,
55
+ protocol: opts.protocol || 'https'
56
+ }
57
+ }
58
+
59
+ module.exports = ProxyAgent
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "undici",
3
- "version": "4.8.1",
3
+ "version": "4.8.2",
4
4
  "description": "An HTTP/1.1 client, written from scratch for Node.js",
5
5
  "homepage": "https://undici.nodejs.org",
6
6
  "bugs": {
@@ -0,0 +1,17 @@
1
+ import Agent = require('./agent')
2
+ import Dispatcher = require('./dispatcher')
3
+
4
+ export = ProxyAgent
5
+
6
+ declare class ProxyAgent extends Dispatcher {
7
+ constructor(options: ProxyAgent.Options | string)
8
+
9
+ dispatch(options: Agent.DispatchOptions, handler: Dispatcher.DispatchHandlers): void;
10
+ close(): Promise<void>;
11
+ }
12
+
13
+ declare namespace ProxyAgent {
14
+ export interface Options extends Agent.Options {
15
+ uri: string;
16
+ }
17
+ }