undici 6.13.0 → 6.14.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/docs/docs/api/EnvHttpProxyAgent.md +162 -0
- package/docs/docs/api/EventSource.md +27 -3
- package/docs/docs/api/Fetch.md +0 -6
- package/index.js +3 -1
- package/lib/api/util.js +14 -7
- package/lib/core/request.js +3 -5
- package/lib/core/symbols.js +4 -1
- package/lib/core/util.js +45 -20
- package/lib/dispatcher/client-h2.js +2 -4
- package/lib/dispatcher/client.js +2 -1
- package/lib/dispatcher/dispatcher-base.js +1 -3
- package/lib/dispatcher/env-http-proxy-agent.js +160 -0
- package/lib/dispatcher/pool.js +1 -1
- package/lib/llhttp/.gitkeep +0 -0
- package/lib/llhttp/llhttp-wasm.js +3 -1
- package/lib/llhttp/llhttp_simd-wasm.js +3 -1
- package/lib/web/cache/cache.js +2 -3
- package/lib/web/eventsource/eventsource.js +35 -38
- package/lib/web/fetch/body.js +12 -10
- package/lib/web/fetch/file.js +5 -216
- package/lib/web/fetch/formdata-parser.js +2 -2
- package/lib/web/fetch/formdata.js +3 -3
- package/lib/web/fetch/index.js +3 -6
- package/lib/web/fetch/request.js +8 -26
- package/lib/web/fetch/response.js +9 -22
- package/lib/web/fetch/symbols.js +0 -1
- package/lib/web/fetch/util.js +34 -8
- package/lib/web/websocket/util.js +6 -13
- package/package.json +4 -3
- package/types/dispatcher.d.ts +1 -1
- package/types/env-http-proxy-agent.d.ts +21 -0
- package/types/eventsource.d.ts +4 -2
- package/types/index.d.ts +2 -1
- package/lib/llhttp/constants.d.ts +0 -199
- package/lib/llhttp/constants.js.map +0 -1
- package/lib/llhttp/utils.d.ts +0 -4
- package/lib/llhttp/utils.js.map +0 -1
- package/lib/llhttp/wasm_build_env.txt +0 -32
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Class: EnvHttpProxyAgent
|
|
2
|
+
|
|
3
|
+
Stability: Experimental.
|
|
4
|
+
|
|
5
|
+
Extends: `undici.Dispatcher`
|
|
6
|
+
|
|
7
|
+
EnvHttpProxyAgent automatically reads the proxy configuration from the environment variables `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` and sets up the proxy agents accordingly. When `HTTP_PROXY` and `HTTPS_PROXY` are set, `HTTP_PROXY` is used for HTTP requests and `HTTPS_PROXY` is used for HTTPS requests. If only `HTTP_PROXY` is set, `HTTP_PROXY` is used for both HTTP and HTTPS requests. If only `HTTPS_PROXY` is set, it is only used for HTTPS requests.
|
|
8
|
+
|
|
9
|
+
`NO_PROXY` is a comma or space-separated list of hostnames that should not be proxied. The list may contain leading wildcard characters (`*`). If `NO_PROXY` is set, the EnvHttpProxyAgent will bypass the proxy for requests to hosts that match the list. If `NO_PROXY` is set to `"*"`, the EnvHttpProxyAgent will bypass the proxy for all requests.
|
|
10
|
+
|
|
11
|
+
Lower case environment variables are also supported: `http_proxy`, `https_proxy`, and `no_proxy`. However, if both the lower case and upper case environment variables are set, the lower case environment variables will be ignored.
|
|
12
|
+
|
|
13
|
+
## `new EnvHttpProxyAgent([options])`
|
|
14
|
+
|
|
15
|
+
Arguments:
|
|
16
|
+
|
|
17
|
+
* **options** `EnvHttpProxyAgentOptions` (optional) - extends the `Agent` options.
|
|
18
|
+
|
|
19
|
+
Returns: `EnvHttpProxyAgent`
|
|
20
|
+
|
|
21
|
+
### Parameter: `EnvHttpProxyAgentOptions`
|
|
22
|
+
|
|
23
|
+
Extends: [`AgentOptions`](Agent.md#parameter-agentoptions)
|
|
24
|
+
|
|
25
|
+
* **httpProxy** `string` (optional) - When set, it will override the `HTTP_PROXY` environment variable.
|
|
26
|
+
* **httpsProxy** `string` (optional) - When set, it will override the `HTTPS_PROXY` environment variable.
|
|
27
|
+
* **noProxy** `string` (optional) - When set, it will override the `NO_PROXY` environment variable.
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
import { EnvHttpProxyAgent } from 'undici'
|
|
33
|
+
|
|
34
|
+
const envHttpProxyAgent = new EnvHttpProxyAgent()
|
|
35
|
+
// or
|
|
36
|
+
const envHttpProxyAgent = new EnvHttpProxyAgent({ httpProxy: 'my.proxy.server:8080', httpsProxy: 'my.proxy.server:8443', noProxy: 'localhost' })
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### Example - EnvHttpProxyAgent instantiation
|
|
40
|
+
|
|
41
|
+
This will instantiate the EnvHttpProxyAgent. It will not do anything until registered as the agent to use with requests.
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
import { EnvHttpProxyAgent } from 'undici'
|
|
45
|
+
|
|
46
|
+
const envHttpProxyAgent = new EnvHttpProxyAgent()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
#### Example - Basic Proxy Fetch with global agent dispatcher
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
import { setGlobalDispatcher, fetch, EnvHttpProxyAgent } from 'undici'
|
|
53
|
+
|
|
54
|
+
const envHttpProxyAgent = new EnvHttpProxyAgent()
|
|
55
|
+
setGlobalDispatcher(envHttpProxyAgent)
|
|
56
|
+
|
|
57
|
+
const { status, json } = await fetch('http://localhost:3000/foo')
|
|
58
|
+
|
|
59
|
+
console.log('response received', status) // response received 200
|
|
60
|
+
|
|
61
|
+
const data = await json() // data { foo: "bar" }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
#### Example - Basic Proxy Request with global agent dispatcher
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
import { setGlobalDispatcher, request, EnvHttpProxyAgent } from 'undici'
|
|
68
|
+
|
|
69
|
+
const envHttpProxyAgent = new EnvHttpProxyAgent()
|
|
70
|
+
setGlobalDispatcher(envHttpProxyAgent)
|
|
71
|
+
|
|
72
|
+
const { statusCode, body } = await request('http://localhost:3000/foo')
|
|
73
|
+
|
|
74
|
+
console.log('response received', statusCode) // response received 200
|
|
75
|
+
|
|
76
|
+
for await (const data of body) {
|
|
77
|
+
console.log('data', data.toString('utf8')) // data foo
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### Example - Basic Proxy Request with local agent dispatcher
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
import { EnvHttpProxyAgent, request } from 'undici'
|
|
85
|
+
|
|
86
|
+
const envHttpProxyAgent = new EnvHttpProxyAgent()
|
|
87
|
+
|
|
88
|
+
const {
|
|
89
|
+
statusCode,
|
|
90
|
+
body
|
|
91
|
+
} = await request('http://localhost:3000/foo', { dispatcher: envHttpProxyAgent })
|
|
92
|
+
|
|
93
|
+
console.log('response received', statusCode) // response received 200
|
|
94
|
+
|
|
95
|
+
for await (const data of body) {
|
|
96
|
+
console.log('data', data.toString('utf8')) // data foo
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### Example - Basic Proxy Fetch with local agent dispatcher
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
import { EnvHttpProxyAgent, fetch } from 'undici'
|
|
104
|
+
|
|
105
|
+
const envHttpProxyAgent = new EnvHttpProxyAgent()
|
|
106
|
+
|
|
107
|
+
const {
|
|
108
|
+
status,
|
|
109
|
+
json
|
|
110
|
+
} = await fetch('http://localhost:3000/foo', { dispatcher: envHttpProxyAgent })
|
|
111
|
+
|
|
112
|
+
console.log('response received', status) // response received 200
|
|
113
|
+
|
|
114
|
+
const data = await json() // data { foo: "bar" }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Instance Methods
|
|
118
|
+
|
|
119
|
+
### `EnvHttpProxyAgent.close([callback])`
|
|
120
|
+
|
|
121
|
+
Implements [`Dispatcher.close([callback])`](Dispatcher.md#dispatcherclosecallback-promise).
|
|
122
|
+
|
|
123
|
+
### `EnvHttpProxyAgent.destroy([error, callback])`
|
|
124
|
+
|
|
125
|
+
Implements [`Dispatcher.destroy([error, callback])`](Dispatcher.md#dispatcherdestroyerror-callback-promise).
|
|
126
|
+
|
|
127
|
+
### `EnvHttpProxyAgent.dispatch(options, handler: AgentDispatchOptions)`
|
|
128
|
+
|
|
129
|
+
Implements [`Dispatcher.dispatch(options, handler)`](Dispatcher.md#dispatcherdispatchoptions-handler).
|
|
130
|
+
|
|
131
|
+
#### Parameter: `AgentDispatchOptions`
|
|
132
|
+
|
|
133
|
+
Extends: [`DispatchOptions`](Dispatcher.md#parameter-dispatchoptions)
|
|
134
|
+
|
|
135
|
+
* **origin** `string | URL`
|
|
136
|
+
* **maxRedirections** `Integer`.
|
|
137
|
+
|
|
138
|
+
Implements [`Dispatcher.destroy([error, callback])`](Dispatcher.md#dispatcherdestroyerror-callback-promise).
|
|
139
|
+
|
|
140
|
+
### `EnvHttpProxyAgent.connect(options[, callback])`
|
|
141
|
+
|
|
142
|
+
See [`Dispatcher.connect(options[, callback])`](Dispatcher.md#dispatcherconnectoptions-callback).
|
|
143
|
+
|
|
144
|
+
### `EnvHttpProxyAgent.dispatch(options, handler)`
|
|
145
|
+
|
|
146
|
+
Implements [`Dispatcher.dispatch(options, handler)`](Dispatcher.md#dispatcherdispatchoptions-handler).
|
|
147
|
+
|
|
148
|
+
### `EnvHttpProxyAgent.pipeline(options, handler)`
|
|
149
|
+
|
|
150
|
+
See [`Dispatcher.pipeline(options, handler)`](Dispatcher.md#dispatcherpipelineoptions-handler).
|
|
151
|
+
|
|
152
|
+
### `EnvHttpProxyAgent.request(options[, callback])`
|
|
153
|
+
|
|
154
|
+
See [`Dispatcher.request(options [, callback])`](Dispatcher.md#dispatcherrequestoptions-callback).
|
|
155
|
+
|
|
156
|
+
### `EnvHttpProxyAgent.stream(options, factory[, callback])`
|
|
157
|
+
|
|
158
|
+
See [`Dispatcher.stream(options, factory[, callback])`](Dispatcher.md#dispatcherstreamoptions-factory-callback).
|
|
159
|
+
|
|
160
|
+
### `EnvHttpProxyAgent.upgrade(options[, callback])`
|
|
161
|
+
|
|
162
|
+
See [`Dispatcher.upgrade(options[, callback])`](Dispatcher.md#dispatcherupgradeoptions-callback).
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# EventSource
|
|
2
2
|
|
|
3
|
+
> ⚠️ Warning: the EventSource API is experimental.
|
|
4
|
+
|
|
3
5
|
Undici exposes a WHATWG spec-compliant implementation of [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource)
|
|
4
6
|
for [Server-Sent Events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events).
|
|
5
7
|
|
|
@@ -11,11 +13,33 @@ follows:
|
|
|
11
13
|
```mjs
|
|
12
14
|
import { EventSource } from 'undici'
|
|
13
15
|
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
+
const eventSource = new EventSource('http://localhost:3000')
|
|
17
|
+
eventSource.onmessage = (event) => {
|
|
16
18
|
console.log(event.data)
|
|
17
19
|
}
|
|
18
20
|
```
|
|
19
21
|
|
|
22
|
+
## Using a custom Dispatcher
|
|
23
|
+
|
|
24
|
+
undici allows you to set your own Dispatcher in the EventSource constructor.
|
|
25
|
+
|
|
26
|
+
An example which allows you to modify the request headers is:
|
|
27
|
+
|
|
28
|
+
```mjs
|
|
29
|
+
import { EventSource, Agent } from 'undici'
|
|
30
|
+
|
|
31
|
+
class CustomHeaderAgent extends Agent {
|
|
32
|
+
dispatch (opts) {
|
|
33
|
+
opts.headers['x-custom-header'] = 'hello world'
|
|
34
|
+
return super.dispatch(...arguments)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const eventSource = new EventSource('http://localhost:3000', {
|
|
39
|
+
dispatcher: new CustomHeaderAgent()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
|
|
20
44
|
More information about the EventSource API can be found on
|
|
21
|
-
[MDN](https://developer.mozilla.org/en-US/docs/Web/API/EventSource).
|
|
45
|
+
[MDN](https://developer.mozilla.org/en-US/docs/Web/API/EventSource).
|
package/docs/docs/api/Fetch.md
CHANGED
|
@@ -4,12 +4,6 @@ Undici exposes a fetch() method starts the process of fetching a resource from t
|
|
|
4
4
|
|
|
5
5
|
Documentation and examples can be found on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/fetch).
|
|
6
6
|
|
|
7
|
-
## File
|
|
8
|
-
|
|
9
|
-
This API is implemented as per the standard, you can find documentation on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/File)
|
|
10
|
-
|
|
11
|
-
In Node versions v18.13.0 and above and v19.2.0 and above, undici will default to using Node's [File](https://nodejs.org/api/buffer.html#class-file) class. In versions where it's not available, it will default to the undici one.
|
|
12
|
-
|
|
13
7
|
## FormData
|
|
14
8
|
|
|
15
9
|
This API is implemented as per the standard, you can find documentation on [MDN](https://developer.mozilla.org/en-US/docs/Web/API/FormData).
|
package/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const Pool = require('./lib/dispatcher/pool')
|
|
|
6
6
|
const BalancedPool = require('./lib/dispatcher/balanced-pool')
|
|
7
7
|
const Agent = require('./lib/dispatcher/agent')
|
|
8
8
|
const ProxyAgent = require('./lib/dispatcher/proxy-agent')
|
|
9
|
+
const EnvHttpProxyAgent = require('./lib/dispatcher/env-http-proxy-agent')
|
|
9
10
|
const RetryAgent = require('./lib/dispatcher/retry-agent')
|
|
10
11
|
const errors = require('./lib/core/errors')
|
|
11
12
|
const util = require('./lib/core/util')
|
|
@@ -30,6 +31,7 @@ module.exports.Pool = Pool
|
|
|
30
31
|
module.exports.BalancedPool = BalancedPool
|
|
31
32
|
module.exports.Agent = Agent
|
|
32
33
|
module.exports.ProxyAgent = ProxyAgent
|
|
34
|
+
module.exports.EnvHttpProxyAgent = EnvHttpProxyAgent
|
|
33
35
|
module.exports.RetryAgent = RetryAgent
|
|
34
36
|
module.exports.RetryHandler = RetryHandler
|
|
35
37
|
|
|
@@ -116,7 +118,7 @@ module.exports.Headers = require('./lib/web/fetch/headers').Headers
|
|
|
116
118
|
module.exports.Response = require('./lib/web/fetch/response').Response
|
|
117
119
|
module.exports.Request = require('./lib/web/fetch/request').Request
|
|
118
120
|
module.exports.FormData = require('./lib/web/fetch/formdata').FormData
|
|
119
|
-
module.exports.File = require('
|
|
121
|
+
module.exports.File = globalThis.File ?? require('node:buffer').File
|
|
120
122
|
module.exports.FileReader = require('./lib/web/fileapi/filereader').FileReader
|
|
121
123
|
|
|
122
124
|
const { setGlobalOrigin, getGlobalOrigin } = require('./lib/web/fetch/global')
|
package/lib/api/util.js
CHANGED
|
@@ -12,18 +12,25 @@ async function getResolveErrorBodyCallback ({ callback, body, contentType, statu
|
|
|
12
12
|
let chunks = []
|
|
13
13
|
let length = 0
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
try {
|
|
16
|
+
for await (const chunk of body) {
|
|
17
|
+
chunks.push(chunk)
|
|
18
|
+
length += chunk.length
|
|
19
|
+
if (length > CHUNK_LIMIT) {
|
|
20
|
+
chunks = []
|
|
21
|
+
length = 0
|
|
22
|
+
break
|
|
23
|
+
}
|
|
21
24
|
}
|
|
25
|
+
} catch {
|
|
26
|
+
chunks = []
|
|
27
|
+
length = 0
|
|
28
|
+
// Do nothing....
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
const message = `Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}`
|
|
25
32
|
|
|
26
|
-
if (statusCode === 204 || !contentType || !
|
|
33
|
+
if (statusCode === 204 || !contentType || !length) {
|
|
27
34
|
queueMicrotask(() => callback(new ResponseStatusCodeError(message, statusCode, headers)))
|
|
28
35
|
return
|
|
29
36
|
}
|
package/lib/core/request.js
CHANGED
|
@@ -7,7 +7,7 @@ const {
|
|
|
7
7
|
const assert = require('node:assert')
|
|
8
8
|
const {
|
|
9
9
|
isValidHTTPToken,
|
|
10
|
-
|
|
10
|
+
isValidHeaderValue,
|
|
11
11
|
isStream,
|
|
12
12
|
destroy,
|
|
13
13
|
isBuffer,
|
|
@@ -336,7 +336,7 @@ function processHeader (request, key, val) {
|
|
|
336
336
|
const arr = []
|
|
337
337
|
for (let i = 0; i < val.length; i++) {
|
|
338
338
|
if (typeof val[i] === 'string') {
|
|
339
|
-
if (!
|
|
339
|
+
if (!isValidHeaderValue(val[i])) {
|
|
340
340
|
throw new InvalidArgumentError(`invalid ${key} header`)
|
|
341
341
|
}
|
|
342
342
|
arr.push(val[i])
|
|
@@ -350,13 +350,11 @@ function processHeader (request, key, val) {
|
|
|
350
350
|
}
|
|
351
351
|
val = arr
|
|
352
352
|
} else if (typeof val === 'string') {
|
|
353
|
-
if (!
|
|
353
|
+
if (!isValidHeaderValue(val)) {
|
|
354
354
|
throw new InvalidArgumentError(`invalid ${key} header`)
|
|
355
355
|
}
|
|
356
356
|
} else if (val === null) {
|
|
357
357
|
val = ''
|
|
358
|
-
} else if (typeof val === 'object') {
|
|
359
|
-
throw new InvalidArgumentError(`invalid ${key} header`)
|
|
360
358
|
} else {
|
|
361
359
|
val = `${val}`
|
|
362
360
|
}
|
package/lib/core/symbols.js
CHANGED
|
@@ -60,5 +60,8 @@ module.exports = {
|
|
|
60
60
|
kConstruct: Symbol('constructable'),
|
|
61
61
|
kListeners: Symbol('listeners'),
|
|
62
62
|
kHTTPContext: Symbol('http context'),
|
|
63
|
-
kMaxConcurrentStreams: Symbol('max concurrent streams')
|
|
63
|
+
kMaxConcurrentStreams: Symbol('max concurrent streams'),
|
|
64
|
+
kNoProxyAgent: Symbol('no proxy agent'),
|
|
65
|
+
kHttpProxyAgent: Symbol('http proxy agent'),
|
|
66
|
+
kHttpsProxyAgent: Symbol('https proxy agent')
|
|
64
67
|
}
|
package/lib/core/util.js
CHANGED
|
@@ -52,11 +52,37 @@ function buildURL (url, queryParams) {
|
|
|
52
52
|
return url
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
function isValidPort (port) {
|
|
56
|
+
const value = parseInt(port, 10)
|
|
57
|
+
return (
|
|
58
|
+
value === Number(port) &&
|
|
59
|
+
value >= 0 &&
|
|
60
|
+
value <= 65535
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isHttpOrHttpsPrefixed (value) {
|
|
65
|
+
return (
|
|
66
|
+
value != null &&
|
|
67
|
+
value[0] === 'h' &&
|
|
68
|
+
value[1] === 't' &&
|
|
69
|
+
value[2] === 't' &&
|
|
70
|
+
value[3] === 'p' &&
|
|
71
|
+
(
|
|
72
|
+
value[4] === ':' ||
|
|
73
|
+
(
|
|
74
|
+
value[4] === 's' &&
|
|
75
|
+
value[5] === ':'
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
55
81
|
function parseURL (url) {
|
|
56
82
|
if (typeof url === 'string') {
|
|
57
83
|
url = new URL(url)
|
|
58
84
|
|
|
59
|
-
if (
|
|
85
|
+
if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) {
|
|
60
86
|
throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.')
|
|
61
87
|
}
|
|
62
88
|
|
|
@@ -67,12 +93,8 @@ function parseURL (url) {
|
|
|
67
93
|
throw new InvalidArgumentError('Invalid URL: The URL argument must be a non-null object.')
|
|
68
94
|
}
|
|
69
95
|
|
|
70
|
-
if (!/^https?:/.test(url.origin || url.protocol)) {
|
|
71
|
-
throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.')
|
|
72
|
-
}
|
|
73
|
-
|
|
74
96
|
if (!(url instanceof URL)) {
|
|
75
|
-
if (url.port != null && url.port !== '' &&
|
|
97
|
+
if (url.port != null && url.port !== '' && isValidPort(url.port) === false) {
|
|
76
98
|
throw new InvalidArgumentError('Invalid URL: port must be a valid integer or a string representation of an integer.')
|
|
77
99
|
}
|
|
78
100
|
|
|
@@ -92,28 +114,36 @@ function parseURL (url) {
|
|
|
92
114
|
throw new InvalidArgumentError('Invalid URL origin: the origin must be a string or null/undefined.')
|
|
93
115
|
}
|
|
94
116
|
|
|
117
|
+
if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) {
|
|
118
|
+
throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.')
|
|
119
|
+
}
|
|
120
|
+
|
|
95
121
|
const port = url.port != null
|
|
96
122
|
? url.port
|
|
97
123
|
: (url.protocol === 'https:' ? 443 : 80)
|
|
98
124
|
let origin = url.origin != null
|
|
99
125
|
? url.origin
|
|
100
|
-
: `${url.protocol}//${url.hostname}:${port}`
|
|
126
|
+
: `${url.protocol || ''}//${url.hostname || ''}:${port}`
|
|
101
127
|
let path = url.path != null
|
|
102
128
|
? url.path
|
|
103
129
|
: `${url.pathname || ''}${url.search || ''}`
|
|
104
130
|
|
|
105
|
-
if (origin.
|
|
106
|
-
origin = origin.
|
|
131
|
+
if (origin[origin.length - 1] === '/') {
|
|
132
|
+
origin = origin.slice(0, origin.length - 1)
|
|
107
133
|
}
|
|
108
134
|
|
|
109
|
-
if (path &&
|
|
135
|
+
if (path && path[0] !== '/') {
|
|
110
136
|
path = `/${path}`
|
|
111
137
|
}
|
|
112
138
|
// new URL(path, origin) is unsafe when `path` contains an absolute URL
|
|
113
139
|
// From https://developer.mozilla.org/en-US/docs/Web/API/URL/URL:
|
|
114
140
|
// If first parameter is a relative URL, second param is required, and will be used as the base URL.
|
|
115
141
|
// If first parameter is an absolute URL, a given second param will be ignored.
|
|
116
|
-
|
|
142
|
+
return new URL(`${origin}${path}`)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) {
|
|
146
|
+
throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.')
|
|
117
147
|
}
|
|
118
148
|
|
|
119
149
|
return url
|
|
@@ -193,11 +223,6 @@ function isDestroyed (body) {
|
|
|
193
223
|
return body && !!(body.destroyed || body[kDestroyed] || (stream.isDestroyed?.(body)))
|
|
194
224
|
}
|
|
195
225
|
|
|
196
|
-
function isReadableAborted (stream) {
|
|
197
|
-
const state = stream?._readableState
|
|
198
|
-
return isDestroyed(stream) && state && !state.endEmitted
|
|
199
|
-
}
|
|
200
|
-
|
|
201
226
|
function destroy (stream, err) {
|
|
202
227
|
if (stream == null || !isStream(stream) || isDestroyed(stream)) {
|
|
203
228
|
return
|
|
@@ -522,7 +547,7 @@ const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/
|
|
|
522
547
|
/**
|
|
523
548
|
* @param {string} characters
|
|
524
549
|
*/
|
|
525
|
-
function
|
|
550
|
+
function isValidHeaderValue (characters) {
|
|
526
551
|
return !headerCharRegex.test(characters)
|
|
527
552
|
}
|
|
528
553
|
|
|
@@ -575,7 +600,6 @@ module.exports = {
|
|
|
575
600
|
isReadable,
|
|
576
601
|
toUSVString,
|
|
577
602
|
isUSVString,
|
|
578
|
-
isReadableAborted,
|
|
579
603
|
isBlobLike,
|
|
580
604
|
parseOrigin,
|
|
581
605
|
parseURL,
|
|
@@ -603,11 +627,12 @@ module.exports = {
|
|
|
603
627
|
buildURL,
|
|
604
628
|
addAbortListener,
|
|
605
629
|
isValidHTTPToken,
|
|
606
|
-
|
|
630
|
+
isValidHeaderValue,
|
|
607
631
|
isTokenCharCode,
|
|
608
632
|
parseRangeHeader,
|
|
633
|
+
isValidPort,
|
|
634
|
+
isHttpOrHttpsPrefixed,
|
|
609
635
|
nodeMajor,
|
|
610
636
|
nodeMinor,
|
|
611
|
-
nodeHasAutoSelectFamily: nodeMajor > 18 || (nodeMajor === 18 && nodeMinor >= 13),
|
|
612
637
|
safeHTTPMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE']
|
|
613
638
|
}
|
|
@@ -208,10 +208,9 @@ function onHttp2SessionEnd () {
|
|
|
208
208
|
* This is the root cause of #3011
|
|
209
209
|
* We need to handle GOAWAY frames properly, and trigger the session close
|
|
210
210
|
* along with the socket right away
|
|
211
|
-
* Find a way to trigger the close cycle from here on.
|
|
212
211
|
*/
|
|
213
212
|
function onHTTP2GoAway (code) {
|
|
214
|
-
const err = new
|
|
213
|
+
const err = new RequestAbortedError(`HTTP/2: "GOAWAY" frame received with code ${code}`)
|
|
215
214
|
|
|
216
215
|
// We need to trigger the close cycle right away
|
|
217
216
|
// We need to destroy the session and the socket
|
|
@@ -220,8 +219,7 @@ function onHTTP2GoAway (code) {
|
|
|
220
219
|
this[kClient][kOnError](err)
|
|
221
220
|
|
|
222
221
|
this.unref()
|
|
223
|
-
|
|
224
|
-
this.destroy()
|
|
222
|
+
|
|
225
223
|
util.destroy(this[kSocket], err)
|
|
226
224
|
}
|
|
227
225
|
|
package/lib/dispatcher/client.js
CHANGED
|
@@ -203,7 +203,7 @@ class Client extends DispatcherBase {
|
|
|
203
203
|
allowH2,
|
|
204
204
|
socketPath,
|
|
205
205
|
timeout: connectTimeout,
|
|
206
|
-
...(
|
|
206
|
+
...(autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined),
|
|
207
207
|
...connect
|
|
208
208
|
})
|
|
209
209
|
}
|
|
@@ -376,6 +376,7 @@ function onError (client, err) {
|
|
|
376
376
|
assert(client[kPendingIdx] === client[kRunningIdx])
|
|
377
377
|
|
|
378
378
|
const requests = client[kQueue].splice(client[kRunningIdx])
|
|
379
|
+
|
|
379
380
|
for (let i = 0; i < requests.length; i++) {
|
|
380
381
|
const request = requests[i]
|
|
381
382
|
util.errorRequest(client, request, err)
|
|
@@ -6,10 +6,8 @@ const {
|
|
|
6
6
|
ClientClosedError,
|
|
7
7
|
InvalidArgumentError
|
|
8
8
|
} = require('../core/errors')
|
|
9
|
-
const { kDestroy, kClose, kDispatch, kInterceptors } = require('../core/symbols')
|
|
9
|
+
const { kDestroy, kClose, kClosed, kDestroyed, kDispatch, kInterceptors } = require('../core/symbols')
|
|
10
10
|
|
|
11
|
-
const kDestroyed = Symbol('destroyed')
|
|
12
|
-
const kClosed = Symbol('closed')
|
|
13
11
|
const kOnDestroyed = Symbol('onDestroyed')
|
|
14
12
|
const kOnClosed = Symbol('onClosed')
|
|
15
13
|
const kInterceptedDispatch = Symbol('Intercepted Dispatch')
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const DispatcherBase = require('./dispatcher-base')
|
|
4
|
+
const { kClose, kDestroy, kClosed, kDestroyed, kDispatch, kNoProxyAgent, kHttpProxyAgent, kHttpsProxyAgent } = require('../core/symbols')
|
|
5
|
+
const ProxyAgent = require('./proxy-agent')
|
|
6
|
+
const Agent = require('./agent')
|
|
7
|
+
|
|
8
|
+
const DEFAULT_PORTS = {
|
|
9
|
+
'http:': 80,
|
|
10
|
+
'https:': 443
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let experimentalWarned = false
|
|
14
|
+
|
|
15
|
+
class EnvHttpProxyAgent extends DispatcherBase {
|
|
16
|
+
#noProxyValue = null
|
|
17
|
+
#noProxyEntries = null
|
|
18
|
+
#opts = null
|
|
19
|
+
|
|
20
|
+
constructor (opts = {}) {
|
|
21
|
+
super()
|
|
22
|
+
this.#opts = opts
|
|
23
|
+
|
|
24
|
+
if (!experimentalWarned) {
|
|
25
|
+
experimentalWarned = true
|
|
26
|
+
process.emitWarning('EnvHttpProxyAgent is experimental, expect them to change at any time.', {
|
|
27
|
+
code: 'UNDICI-EHPA'
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { httpProxy, httpsProxy, noProxy, ...agentOpts } = opts
|
|
32
|
+
|
|
33
|
+
this[kNoProxyAgent] = new Agent(agentOpts)
|
|
34
|
+
|
|
35
|
+
const HTTP_PROXY = httpProxy ?? process.env.HTTP_PROXY ?? process.env.http_proxy
|
|
36
|
+
if (HTTP_PROXY) {
|
|
37
|
+
this[kHttpProxyAgent] = new ProxyAgent({ ...agentOpts, uri: HTTP_PROXY })
|
|
38
|
+
} else {
|
|
39
|
+
this[kHttpProxyAgent] = this[kNoProxyAgent]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const HTTPS_PROXY = httpsProxy ?? process.env.HTTPS_PROXY ?? process.env.https_proxy
|
|
43
|
+
if (HTTPS_PROXY) {
|
|
44
|
+
this[kHttpsProxyAgent] = new ProxyAgent({ ...agentOpts, uri: HTTPS_PROXY })
|
|
45
|
+
} else {
|
|
46
|
+
this[kHttpsProxyAgent] = this[kHttpProxyAgent]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.#parseNoProxy()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
[kDispatch] (opts, handler) {
|
|
53
|
+
const url = new URL(opts.origin)
|
|
54
|
+
const agent = this.#getProxyAgentForUrl(url)
|
|
55
|
+
return agent.dispatch(opts, handler)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async [kClose] () {
|
|
59
|
+
await this[kNoProxyAgent].close()
|
|
60
|
+
if (!this[kHttpProxyAgent][kClosed]) {
|
|
61
|
+
await this[kHttpProxyAgent].close()
|
|
62
|
+
}
|
|
63
|
+
if (!this[kHttpsProxyAgent][kClosed]) {
|
|
64
|
+
await this[kHttpsProxyAgent].close()
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async [kDestroy] (err) {
|
|
69
|
+
await this[kNoProxyAgent].destroy(err)
|
|
70
|
+
if (!this[kHttpProxyAgent][kDestroyed]) {
|
|
71
|
+
await this[kHttpProxyAgent].destroy(err)
|
|
72
|
+
}
|
|
73
|
+
if (!this[kHttpsProxyAgent][kDestroyed]) {
|
|
74
|
+
await this[kHttpsProxyAgent].destroy(err)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#getProxyAgentForUrl (url) {
|
|
79
|
+
let { protocol, host: hostname, port } = url
|
|
80
|
+
|
|
81
|
+
// Stripping ports in this way instead of using parsedUrl.hostname to make
|
|
82
|
+
// sure that the brackets around IPv6 addresses are kept.
|
|
83
|
+
hostname = hostname.replace(/:\d*$/, '').toLowerCase()
|
|
84
|
+
port = Number.parseInt(port, 10) || DEFAULT_PORTS[protocol] || 0
|
|
85
|
+
if (!this.#shouldProxy(hostname, port)) {
|
|
86
|
+
return this[kNoProxyAgent]
|
|
87
|
+
}
|
|
88
|
+
if (protocol === 'https:') {
|
|
89
|
+
return this[kHttpsProxyAgent]
|
|
90
|
+
}
|
|
91
|
+
return this[kHttpProxyAgent]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#shouldProxy (hostname, port) {
|
|
95
|
+
if (this.#noProxyChanged) {
|
|
96
|
+
this.#parseNoProxy()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (this.#noProxyEntries.length === 0) {
|
|
100
|
+
return true // Always proxy if NO_PROXY is not set or empty.
|
|
101
|
+
}
|
|
102
|
+
if (this.#noProxyValue === '*') {
|
|
103
|
+
return false // Never proxy if wildcard is set.
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (let i = 0; i < this.#noProxyEntries.length; i++) {
|
|
107
|
+
const entry = this.#noProxyEntries[i]
|
|
108
|
+
if (entry.port && entry.port !== port) {
|
|
109
|
+
continue // Skip if ports don't match.
|
|
110
|
+
}
|
|
111
|
+
if (!/^[.*]/.test(entry.hostname)) {
|
|
112
|
+
// No wildcards, so don't proxy only if there is not an exact match.
|
|
113
|
+
if (hostname === entry.hostname) {
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
// Don't proxy if the hostname ends with the no_proxy host.
|
|
118
|
+
if (hostname.endsWith(entry.hostname.replace(/^\*/, ''))) {
|
|
119
|
+
return false
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return true
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#parseNoProxy () {
|
|
128
|
+
const noProxyValue = this.#opts.noProxy ?? this.#noProxyEnv
|
|
129
|
+
const noProxySplit = noProxyValue.split(/[,\s]/)
|
|
130
|
+
const noProxyEntries = []
|
|
131
|
+
|
|
132
|
+
for (let i = 0; i < noProxySplit.length; i++) {
|
|
133
|
+
const entry = noProxySplit[i]
|
|
134
|
+
if (!entry) {
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
const parsed = entry.match(/^(.+):(\d+)$/)
|
|
138
|
+
noProxyEntries.push({
|
|
139
|
+
hostname: (parsed ? parsed[1] : entry).toLowerCase(),
|
|
140
|
+
port: parsed ? Number.parseInt(parsed[2], 10) : 0
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.#noProxyValue = noProxyValue
|
|
145
|
+
this.#noProxyEntries = noProxyEntries
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
get #noProxyChanged () {
|
|
149
|
+
if (this.#opts.noProxy !== undefined) {
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
return this.#noProxyValue !== this.#noProxyEnv
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
get #noProxyEnv () {
|
|
156
|
+
return process.env.NO_PROXY ?? process.env.no_proxy ?? ''
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = EnvHttpProxyAgent
|
package/lib/dispatcher/pool.js
CHANGED
|
@@ -58,7 +58,7 @@ class Pool extends PoolBase {
|
|
|
58
58
|
allowH2,
|
|
59
59
|
socketPath,
|
|
60
60
|
timeout: connectTimeout,
|
|
61
|
-
...(
|
|
61
|
+
...(autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined),
|
|
62
62
|
...connect
|
|
63
63
|
})
|
|
64
64
|
}
|
|
File without changes
|