undici 7.9.0 → 7.11.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.
Files changed (66) hide show
  1. package/README.md +157 -0
  2. package/docs/docs/api/CacheStore.md +23 -3
  3. package/docs/docs/api/Debug.md +13 -13
  4. package/docs/docs/api/DiagnosticsChannel.md +25 -0
  5. package/docs/docs/api/Dispatcher.md +20 -1
  6. package/docs/docs/api/GlobalInstallation.md +91 -0
  7. package/docs/docs/api/MockClient.md +4 -0
  8. package/docs/docs/api/MockPool.md +6 -0
  9. package/docs/docs/api/Pool.md +1 -0
  10. package/docs/docs/api/ProxyAgent.md +3 -0
  11. package/docs/docs/api/RetryAgent.md +6 -1
  12. package/docs/docs/api/RetryHandler.md +1 -0
  13. package/index.js +15 -0
  14. package/lib/api/api-stream.js +1 -1
  15. package/lib/cache/memory-cache-store.js +42 -4
  16. package/lib/cache/sqlite-cache-store.js +1 -1
  17. package/lib/core/connect.js +21 -51
  18. package/lib/core/diagnostics.js +6 -4
  19. package/lib/core/request.js +6 -0
  20. package/lib/core/util.js +0 -45
  21. package/lib/dispatcher/agent.js +25 -15
  22. package/lib/dispatcher/client-h1.js +1 -1
  23. package/lib/dispatcher/pool.js +17 -3
  24. package/lib/dispatcher/proxy-agent.js +90 -3
  25. package/lib/handler/retry-handler.js +110 -56
  26. package/lib/mock/mock-agent.js +8 -8
  27. package/lib/mock/mock-client.js +4 -0
  28. package/lib/mock/mock-pool.js +4 -0
  29. package/lib/util/cache.js +11 -1
  30. package/lib/util/timers.js +11 -9
  31. package/lib/web/cache/cache.js +1 -1
  32. package/lib/web/cache/cachestorage.js +1 -1
  33. package/lib/web/cookies/index.js +1 -1
  34. package/lib/web/eventsource/eventsource.js +3 -6
  35. package/lib/web/eventsource/util.js +1 -1
  36. package/lib/web/fetch/body.js +2 -2
  37. package/lib/web/fetch/dispatcher-weakref.js +0 -41
  38. package/lib/web/fetch/formdata-parser.js +4 -4
  39. package/lib/web/fetch/formdata.js +1 -1
  40. package/lib/web/fetch/headers.js +1 -1
  41. package/lib/web/fetch/index.js +7 -1
  42. package/lib/web/fetch/request.js +1 -1
  43. package/lib/web/fetch/response.js +1 -1
  44. package/lib/web/fetch/util.js +2 -2
  45. package/lib/web/{fetch/webidl.js → webidl/index.js} +57 -9
  46. package/lib/web/websocket/connection.js +4 -3
  47. package/lib/web/websocket/events.js +1 -1
  48. package/lib/web/websocket/frame.js +2 -1
  49. package/lib/web/websocket/stream/websocketerror.js +1 -1
  50. package/lib/web/websocket/stream/websocketstream.js +1 -1
  51. package/lib/web/websocket/websocket.js +4 -4
  52. package/package.json +4 -4
  53. package/types/diagnostics-channel.d.ts +9 -0
  54. package/types/dispatcher.d.ts +3 -2
  55. package/types/env-http-proxy-agent.d.ts +2 -1
  56. package/types/eventsource.d.ts +3 -3
  57. package/types/fetch.d.ts +1 -0
  58. package/types/handlers.d.ts +1 -1
  59. package/types/mock-client.d.ts +2 -0
  60. package/types/mock-interceptor.d.ts +2 -0
  61. package/types/mock-pool.d.ts +2 -0
  62. package/types/pool.d.ts +2 -0
  63. package/types/proxy-agent.d.ts +1 -0
  64. package/types/retry-handler.d.ts +9 -0
  65. package/types/webidl.d.ts +19 -15
  66. package/types/websocket.d.ts +1 -1
@@ -12,64 +12,34 @@ let tls // include tls conditionally since it is not always available
12
12
  // resolve the same servername multiple times even when
13
13
  // re-use is enabled.
14
14
 
15
- let SessionCache
16
- // FIXME: remove workaround when the Node bug is fixed
17
- // https://github.com/nodejs/node/issues/49344#issuecomment-1741776308
18
- if (global.FinalizationRegistry && !(process.env.NODE_V8_COVERAGE || process.env.UNDICI_NO_FG)) {
19
- SessionCache = class WeakSessionCache {
20
- constructor (maxCachedSessions) {
21
- this._maxCachedSessions = maxCachedSessions
22
- this._sessionCache = new Map()
23
- this._sessionRegistry = new global.FinalizationRegistry((key) => {
24
- if (this._sessionCache.size < this._maxCachedSessions) {
25
- return
26
- }
27
-
28
- const ref = this._sessionCache.get(key)
29
- if (ref !== undefined && ref.deref() === undefined) {
30
- this._sessionCache.delete(key)
31
- }
32
- })
33
- }
34
-
35
- get (sessionKey) {
36
- const ref = this._sessionCache.get(sessionKey)
37
- return ref ? ref.deref() : null
38
- }
39
-
40
- set (sessionKey, session) {
41
- if (this._maxCachedSessions === 0) {
15
+ const SessionCache = class WeakSessionCache {
16
+ constructor (maxCachedSessions) {
17
+ this._maxCachedSessions = maxCachedSessions
18
+ this._sessionCache = new Map()
19
+ this._sessionRegistry = new FinalizationRegistry((key) => {
20
+ if (this._sessionCache.size < this._maxCachedSessions) {
42
21
  return
43
22
  }
44
23
 
45
- this._sessionCache.set(sessionKey, new WeakRef(session))
46
- this._sessionRegistry.register(session, sessionKey)
47
- }
48
- }
49
- } else {
50
- SessionCache = class SimpleSessionCache {
51
- constructor (maxCachedSessions) {
52
- this._maxCachedSessions = maxCachedSessions
53
- this._sessionCache = new Map()
54
- }
55
-
56
- get (sessionKey) {
57
- return this._sessionCache.get(sessionKey)
58
- }
59
-
60
- set (sessionKey, session) {
61
- if (this._maxCachedSessions === 0) {
62
- return
24
+ const ref = this._sessionCache.get(key)
25
+ if (ref !== undefined && ref.deref() === undefined) {
26
+ this._sessionCache.delete(key)
63
27
  }
28
+ })
29
+ }
64
30
 
65
- if (this._sessionCache.size >= this._maxCachedSessions) {
66
- // remove the oldest session
67
- const { value: oldestKey } = this._sessionCache.keys().next()
68
- this._sessionCache.delete(oldestKey)
69
- }
31
+ get (sessionKey) {
32
+ const ref = this._sessionCache.get(sessionKey)
33
+ return ref ? ref.deref() : null
34
+ }
70
35
 
71
- this._sessionCache.set(sessionKey, session)
36
+ set (sessionKey, session) {
37
+ if (this._maxCachedSessions === 0) {
38
+ return
72
39
  }
40
+
41
+ this._sessionCache.set(sessionKey, new WeakRef(session))
42
+ this._sessionRegistry.register(session, sessionKey)
73
43
  }
74
44
  }
75
45
 
@@ -16,6 +16,8 @@ const channels = {
16
16
  // Request
17
17
  create: diagnosticsChannel.channel('undici:request:create'),
18
18
  bodySent: diagnosticsChannel.channel('undici:request:bodySent'),
19
+ bodyChunkSent: diagnosticsChannel.channel('undici:request:bodyChunkSent'),
20
+ bodyChunkReceived: diagnosticsChannel.channel('undici:request:bodyChunkReceived'),
19
21
  headers: diagnosticsChannel.channel('undici:request:headers'),
20
22
  trailers: diagnosticsChannel.channel('undici:request:trailers'),
21
23
  error: diagnosticsChannel.channel('undici:request:error'),
@@ -85,7 +87,7 @@ function trackClientEvents (debugLog = undiciDebugLog) {
85
87
  const {
86
88
  request: { method, path, origin }
87
89
  } = evt
88
- debugLog('sending request to %s %s/%s', method, origin, path)
90
+ debugLog('sending request to %s %s%s', method, origin, path)
89
91
  })
90
92
  }
91
93
 
@@ -105,7 +107,7 @@ function trackRequestEvents (debugLog = undiciDebugLog) {
105
107
  response: { statusCode }
106
108
  } = evt
107
109
  debugLog(
108
- 'received response to %s %s/%s - HTTP %d',
110
+ 'received response to %s %s%s - HTTP %d',
109
111
  method,
110
112
  origin,
111
113
  path,
@@ -118,7 +120,7 @@ function trackRequestEvents (debugLog = undiciDebugLog) {
118
120
  const {
119
121
  request: { method, path, origin }
120
122
  } = evt
121
- debugLog('trailers received from %s %s/%s', method, origin, path)
123
+ debugLog('trailers received from %s %s%s', method, origin, path)
122
124
  })
123
125
 
124
126
  diagnosticsChannel.subscribe('undici:request:error',
@@ -128,7 +130,7 @@ function trackRequestEvents (debugLog = undiciDebugLog) {
128
130
  error
129
131
  } = evt
130
132
  debugLog(
131
- 'request to %s %s/%s errored - %s',
133
+ 'request to %s %s%s errored - %s',
132
134
  method,
133
135
  origin,
134
136
  path,
@@ -194,6 +194,9 @@ class Request {
194
194
  }
195
195
 
196
196
  onBodySent (chunk) {
197
+ if (channels.bodyChunkSent.hasSubscribers) {
198
+ channels.bodyChunkSent.publish({ request: this, chunk })
199
+ }
197
200
  if (this[kHandler].onBodySent) {
198
201
  try {
199
202
  return this[kHandler].onBodySent(chunk)
@@ -252,6 +255,9 @@ class Request {
252
255
  assert(!this.aborted)
253
256
  assert(!this.completed)
254
257
 
258
+ if (channels.bodyChunkReceived.hasSubscribers) {
259
+ channels.bodyChunkReceived.publish({ request: this, chunk })
260
+ }
255
261
  try {
256
262
  return this[kHandler].onData(chunk)
257
263
  } catch (err) {
package/lib/core/util.js CHANGED
@@ -6,7 +6,6 @@ const { IncomingMessage } = require('node:http')
6
6
  const stream = require('node:stream')
7
7
  const net = require('node:net')
8
8
  const { Blob } = require('node:buffer')
9
- const nodeUtil = require('node:util')
10
9
  const { stringify } = require('node:querystring')
11
10
  const { EventEmitter: EE } = require('node:events')
12
11
  const timers = require('../util/timers')
@@ -660,48 +659,6 @@ function addAbortListener (signal, listener) {
660
659
  return () => signal.removeListener('abort', listener)
661
660
  }
662
661
 
663
- /**
664
- * @function
665
- * @param {string} value
666
- * @returns {string}
667
- */
668
- const toUSVString = (() => {
669
- if (typeof String.prototype.toWellFormed === 'function') {
670
- /**
671
- * @param {string} value
672
- * @returns {string}
673
- */
674
- return (value) => `${value}`.toWellFormed()
675
- } else {
676
- /**
677
- * @param {string} value
678
- * @returns {string}
679
- */
680
- return nodeUtil.toUSVString
681
- }
682
- })()
683
-
684
- /**
685
- * @param {*} value
686
- * @returns {boolean}
687
- */
688
- // TODO: move this to webidl
689
- const isUSVString = (() => {
690
- if (typeof String.prototype.isWellFormed === 'function') {
691
- /**
692
- * @param {*} value
693
- * @returns {boolean}
694
- */
695
- return (value) => `${value}`.isWellFormed()
696
- } else {
697
- /**
698
- * @param {*} value
699
- * @returns {boolean}
700
- */
701
- return (value) => toUSVString(value) === `${value}`
702
- }
703
- })()
704
-
705
662
  /**
706
663
  * @see https://tools.ietf.org/html/rfc7230#section-3.2.6
707
664
  * @param {number} c
@@ -943,8 +900,6 @@ Object.setPrototypeOf(normalizedMethodRecords, null)
943
900
  module.exports = {
944
901
  kEnumerableProperty,
945
902
  isDisturbed,
946
- toUSVString,
947
- isUSVString,
948
903
  isBlobLike,
949
904
  parseOrigin,
950
905
  parseURL,
@@ -45,22 +45,35 @@ class Agent extends DispatcherBase {
45
45
  }
46
46
 
47
47
  this[kOnConnect] = (origin, targets) => {
48
+ const result = this[kClients].get(origin)
49
+ if (result) {
50
+ result.count += 1
51
+ }
48
52
  this.emit('connect', origin, [this, ...targets])
49
53
  }
50
54
 
51
55
  this[kOnDisconnect] = (origin, targets, err) => {
56
+ const result = this[kClients].get(origin)
57
+ if (result) {
58
+ result.count -= 1
59
+ if (result.count <= 0) {
60
+ this[kClients].delete(origin)
61
+ result.dispatcher.destroy()
62
+ }
63
+ }
52
64
  this.emit('disconnect', origin, [this, ...targets], err)
53
65
  }
54
66
 
55
67
  this[kOnConnectionError] = (origin, targets, err) => {
68
+ // TODO: should this decrement result.count here?
56
69
  this.emit('connectionError', origin, [this, ...targets], err)
57
70
  }
58
71
  }
59
72
 
60
73
  get [kRunning] () {
61
74
  let ret = 0
62
- for (const client of this[kClients].values()) {
63
- ret += client[kRunning]
75
+ for (const { dispatcher } of this[kClients].values()) {
76
+ ret += dispatcher[kRunning]
64
77
  }
65
78
  return ret
66
79
  }
@@ -73,8 +86,8 @@ class Agent extends DispatcherBase {
73
86
  throw new InvalidArgumentError('opts.origin must be a non-empty string or URL.')
74
87
  }
75
88
 
76
- let dispatcher = this[kClients].get(key)
77
-
89
+ const result = this[kClients].get(key)
90
+ let dispatcher = result && result.dispatcher
78
91
  if (!dispatcher) {
79
92
  dispatcher = this[kFactory](opts.origin, this[kOptions])
80
93
  .on('drain', this[kOnDrain])
@@ -82,10 +95,7 @@ class Agent extends DispatcherBase {
82
95
  .on('disconnect', this[kOnDisconnect])
83
96
  .on('connectionError', this[kOnConnectionError])
84
97
 
85
- // This introduces a tiny memory leak, as dispatchers are never removed from the map.
86
- // TODO(mcollina): remove te timer when the client/pool do not have any more
87
- // active connections.
88
- this[kClients].set(key, dispatcher)
98
+ this[kClients].set(key, { count: 0, dispatcher })
89
99
  }
90
100
 
91
101
  return dispatcher.dispatch(opts, handler)
@@ -93,8 +103,8 @@ class Agent extends DispatcherBase {
93
103
 
94
104
  async [kClose] () {
95
105
  const closePromises = []
96
- for (const client of this[kClients].values()) {
97
- closePromises.push(client.close())
106
+ for (const { dispatcher } of this[kClients].values()) {
107
+ closePromises.push(dispatcher.close())
98
108
  }
99
109
  this[kClients].clear()
100
110
 
@@ -103,8 +113,8 @@ class Agent extends DispatcherBase {
103
113
 
104
114
  async [kDestroy] (err) {
105
115
  const destroyPromises = []
106
- for (const client of this[kClients].values()) {
107
- destroyPromises.push(client.destroy(err))
116
+ for (const { dispatcher } of this[kClients].values()) {
117
+ destroyPromises.push(dispatcher.destroy(err))
108
118
  }
109
119
  this[kClients].clear()
110
120
 
@@ -113,9 +123,9 @@ class Agent extends DispatcherBase {
113
123
 
114
124
  get stats () {
115
125
  const allClientStats = {}
116
- for (const client of this[kClients].values()) {
117
- if (client.stats) {
118
- allClientStats[client[kUrl].origin] = client.stats
126
+ for (const { dispatcher } of this[kClients].values()) {
127
+ if (dispatcher.stats) {
128
+ allClientStats[dispatcher[kUrl].origin] = dispatcher.stats
119
129
  }
120
130
  }
121
131
  return allClientStats
@@ -249,7 +249,7 @@ class Parser {
249
249
  this.timeout = timers.setFastTimeout(onParserTimeout, delay, new WeakRef(this))
250
250
  } else {
251
251
  this.timeout = setTimeout(onParserTimeout, delay, new WeakRef(this))
252
- this.timeout.unref()
252
+ this.timeout?.unref()
253
253
  }
254
254
  }
255
255
 
@@ -5,7 +5,8 @@ const {
5
5
  kClients,
6
6
  kNeedDrain,
7
7
  kAddClient,
8
- kGetDispatcher
8
+ kGetDispatcher,
9
+ kRemoveClient
9
10
  } = require('./pool-base')
10
11
  const Client = require('./client')
11
12
  const {
@@ -35,6 +36,7 @@ class Pool extends PoolBase {
35
36
  autoSelectFamily,
36
37
  autoSelectFamilyAttemptTimeout,
37
38
  allowH2,
39
+ clientTtl,
38
40
  ...options
39
41
  } = {}) {
40
42
  if (connections != null && (!Number.isFinite(connections) || connections < 0)) {
@@ -65,12 +67,20 @@ class Pool extends PoolBase {
65
67
 
66
68
  this[kConnections] = connections || null
67
69
  this[kUrl] = util.parseOrigin(origin)
68
- this[kOptions] = { ...util.deepClone(options), connect, allowH2 }
70
+ this[kOptions] = { ...util.deepClone(options), connect, allowH2, clientTtl }
69
71
  this[kOptions].interceptors = options.interceptors
70
72
  ? { ...options.interceptors }
71
73
  : undefined
72
74
  this[kFactory] = factory
73
75
 
76
+ this.on('connect', (origin, targets) => {
77
+ if (clientTtl != null && clientTtl > 0) {
78
+ for (const target of targets) {
79
+ Object.assign(target, { ttl: Date.now() })
80
+ }
81
+ }
82
+ })
83
+
74
84
  this.on('connectionError', (origin, targets, error) => {
75
85
  // If a connection error occurs, we remove the client from the pool,
76
86
  // and emit a connectionError event. They will not be re-used.
@@ -87,8 +97,12 @@ class Pool extends PoolBase {
87
97
  }
88
98
 
89
99
  [kGetDispatcher] () {
100
+ const clientTtlOption = this[kOptions].clientTtl
90
101
  for (const client of this[kClients]) {
91
- if (!client[kNeedDrain]) {
102
+ // check ttl of client and if it's stale, remove it from the pool
103
+ if (clientTtlOption != null && clientTtlOption > 0 && client.ttl && ((Date.now() - client.ttl) > clientTtlOption)) {
104
+ this[kRemoveClient](client)
105
+ } else if (!client[kNeedDrain]) {
92
106
  return client
93
107
  }
94
108
  }
@@ -1,12 +1,13 @@
1
1
  'use strict'
2
2
 
3
- const { kProxy, kClose, kDestroy } = require('../core/symbols')
3
+ const { kProxy, kClose, kDestroy, kDispatch, kConnector } = require('../core/symbols')
4
4
  const { URL } = require('node:url')
5
5
  const Agent = require('./agent')
6
6
  const Pool = require('./pool')
7
7
  const DispatcherBase = require('./dispatcher-base')
8
8
  const { InvalidArgumentError, RequestAbortedError, SecureProxyConnectionError } = require('../core/errors')
9
9
  const buildConnector = require('../core/connect')
10
+ const Client = require('./client')
10
11
 
11
12
  const kAgent = Symbol('proxy agent')
12
13
  const kClient = Symbol('proxy client')
@@ -14,6 +15,7 @@ const kProxyHeaders = Symbol('proxy headers')
14
15
  const kRequestTls = Symbol('request tls settings')
15
16
  const kProxyTls = Symbol('proxy tls settings')
16
17
  const kConnectEndpoint = Symbol('connect endpoint function')
18
+ const kTunnelProxy = Symbol('tunnel proxy')
17
19
 
18
20
  function defaultProtocolPort (protocol) {
19
21
  return protocol === 'https:' ? 443 : 80
@@ -25,6 +27,61 @@ function defaultFactory (origin, opts) {
25
27
 
26
28
  const noop = () => {}
27
29
 
30
+ class ProxyClient extends DispatcherBase {
31
+ #client = null
32
+ constructor (origin, opts) {
33
+ if (typeof origin === 'string') {
34
+ origin = new URL(origin)
35
+ }
36
+
37
+ if (origin.protocol !== 'http:' && origin.protocol !== 'https:') {
38
+ throw new InvalidArgumentError('ProxyClient only supports http and https protocols')
39
+ }
40
+
41
+ super()
42
+
43
+ this.#client = new Client(origin, opts)
44
+ }
45
+
46
+ async [kClose] () {
47
+ await this.#client.close()
48
+ }
49
+
50
+ async [kDestroy] () {
51
+ await this.#client.destroy()
52
+ }
53
+
54
+ async [kDispatch] (opts, handler) {
55
+ const { method, origin } = opts
56
+ if (method === 'CONNECT') {
57
+ this.#client[kConnector]({
58
+ origin,
59
+ port: opts.port || defaultProtocolPort(opts.protocol),
60
+ path: opts.host,
61
+ signal: opts.signal,
62
+ headers: {
63
+ ...this[kProxyHeaders],
64
+ host: opts.host
65
+ },
66
+ servername: this[kProxyTls]?.servername || opts.servername
67
+ },
68
+ (err, socket) => {
69
+ if (err) {
70
+ handler.callback(err)
71
+ } else {
72
+ handler.callback(null, { socket, statusCode: 200 })
73
+ }
74
+ }
75
+ )
76
+ return
77
+ }
78
+ if (typeof origin === 'string') {
79
+ opts.origin = new URL(origin)
80
+ }
81
+
82
+ return this.#client.dispatch(opts, handler)
83
+ }
84
+ }
28
85
  class ProxyAgent extends DispatcherBase {
29
86
  constructor (opts) {
30
87
  if (!opts || (typeof opts === 'object' && !(opts instanceof URL) && !opts.uri)) {
@@ -36,6 +93,8 @@ class ProxyAgent extends DispatcherBase {
36
93
  throw new InvalidArgumentError('Proxy opts.clientFactory must be a function.')
37
94
  }
38
95
 
96
+ const { proxyTunnel = true } = opts
97
+
39
98
  super()
40
99
 
41
100
  const url = this.#getUrl(opts)
@@ -57,9 +116,19 @@ class ProxyAgent extends DispatcherBase {
57
116
  this[kProxyHeaders]['proxy-authorization'] = `Basic ${Buffer.from(`${decodeURIComponent(username)}:${decodeURIComponent(password)}`).toString('base64')}`
58
117
  }
59
118
 
119
+ const factory = (!proxyTunnel && protocol === 'http:')
120
+ ? (origin, options) => {
121
+ if (origin.protocol === 'http:') {
122
+ return new ProxyClient(origin, options)
123
+ }
124
+ return new Client(origin, options)
125
+ }
126
+ : undefined
127
+
60
128
  const connect = buildConnector({ ...opts.proxyTls })
61
129
  this[kConnectEndpoint] = buildConnector({ ...opts.requestTls })
62
- this[kClient] = clientFactory(url, { connect })
130
+ this[kClient] = clientFactory(url, { connect, factory })
131
+ this[kTunnelProxy] = proxyTunnel
63
132
  this[kAgent] = new Agent({
64
133
  ...opts,
65
134
  connect: async (opts, callback) => {
@@ -75,7 +144,8 @@ class ProxyAgent extends DispatcherBase {
75
144
  signal: opts.signal,
76
145
  headers: {
77
146
  ...this[kProxyHeaders],
78
- host: opts.host
147
+ host: opts.host,
148
+ ...(opts.connections == null || opts.connections > 0 ? { 'proxy-connection': 'keep-alive' } : {})
79
149
  },
80
150
  servername: this[kProxyTls]?.servername || proxyHostname
81
151
  })
@@ -115,6 +185,10 @@ class ProxyAgent extends DispatcherBase {
115
185
  headers.host = host
116
186
  }
117
187
 
188
+ if (!this.#shouldConnect(new URL(opts.origin))) {
189
+ opts.path = opts.origin + opts.path
190
+ }
191
+
118
192
  return this[kAgent].dispatch(
119
193
  {
120
194
  ...opts,
@@ -147,6 +221,19 @@ class ProxyAgent extends DispatcherBase {
147
221
  await this[kAgent].destroy()
148
222
  await this[kClient].destroy()
149
223
  }
224
+
225
+ #shouldConnect (uri) {
226
+ if (typeof uri === 'string') {
227
+ uri = new URL(uri)
228
+ }
229
+ if (this[kTunnelProxy]) {
230
+ return true
231
+ }
232
+ if (uri.protocol !== 'http:' || this[kProxy].protocol !== 'http:') {
233
+ return true
234
+ }
235
+ return false
236
+ }
150
237
  }
151
238
 
152
239
  /**