global-agent 3.0.0 → 4.1.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/.babelrc +23 -0
- package/.editorconfig +9 -0
- package/.eslintignore +1 -0
- package/.eslintrc +27 -0
- package/.github/FUNDING.yml +2 -0
- package/.github/workflows/feature.yaml +35 -0
- package/.github/workflows/main.yaml +50 -0
- package/.gitignore +14 -0
- package/LICENSE +2 -2
- package/README.md +101 -23
- package/package.json +47 -56
- package/src/Logger.ts +70 -0
- package/{dist/classes/Agent.js.flow → src/classes/Agent.ts} +124 -40
- package/src/classes/HttpProxyAgent.ts +45 -0
- package/src/classes/HttpsProxyAgent.ts +83 -0
- package/src/classes/index.ts +9 -0
- package/src/{errors.js → errors.ts} +2 -6
- package/src/factories/{createGlobalProxyAgent.js → createGlobalProxyAgent.ts} +45 -52
- package/src/factories/{createProxyController.js → createProxyController.ts} +12 -7
- package/src/factories/index.ts +6 -0
- package/src/index.ts +9 -0
- package/src/routines/bootstrap.ts +28 -0
- package/src/routines/index.ts +3 -0
- package/src/types.ts +70 -0
- package/src/utilities/{bindHttpMethod.js → bindHttpMethod.ts} +6 -7
- package/src/utilities/index.ts +9 -0
- package/{dist/utilities/isUrlMatchingNoProxy.js.flow → src/utilities/isUrlMatchingNoProxy.ts} +1 -6
- package/src/utilities/parseBoolean.ts +17 -0
- package/src/utilities/{parseProxyUrl.js → parseProxyUrl.ts} +12 -9
- package/test/.eslintrc +10 -0
- package/test/global-agent/factories/createGlobalProxyAgent.ts +760 -0
- package/test/global-agent/factories/createProxyController.ts +37 -0
- package/test/global-agent/utilities/isUrlMatchingNoProxy.ts +62 -0
- package/test/global-agent/utilities/parseProxyUrl.ts +38 -0
- package/tsconfig.json +25 -0
- package/.flowconfig +0 -3
- package/dist/Logger.js +0 -18
- package/dist/Logger.js.flow +0 -10
- package/dist/Logger.js.map +0 -1
- package/dist/classes/Agent.js +0 -174
- package/dist/classes/Agent.js.map +0 -1
- package/dist/classes/HttpProxyAgent.js +0 -33
- package/dist/classes/HttpProxyAgent.js.flow +0 -30
- package/dist/classes/HttpProxyAgent.js.map +0 -1
- package/dist/classes/HttpsProxyAgent.js +0 -53
- package/dist/classes/HttpsProxyAgent.js.flow +0 -54
- package/dist/classes/HttpsProxyAgent.js.map +0 -1
- package/dist/classes/index.js +0 -32
- package/dist/classes/index.js.flow +0 -5
- package/dist/classes/index.js.map +0 -1
- package/dist/errors.js +0 -22
- package/dist/errors.js.flow +0 -15
- package/dist/errors.js.map +0 -1
- package/dist/factories/createGlobalProxyAgent.js +0 -175
- package/dist/factories/createGlobalProxyAgent.js.flow +0 -197
- package/dist/factories/createGlobalProxyAgent.js.map +0 -1
- package/dist/factories/createProxyController.js +0 -45
- package/dist/factories/createProxyController.js.flow +0 -46
- package/dist/factories/createProxyController.js.map +0 -1
- package/dist/factories/index.js +0 -24
- package/dist/factories/index.js.flow +0 -4
- package/dist/factories/index.js.map +0 -1
- package/dist/index.js +0 -22
- package/dist/index.js.flow +0 -4
- package/dist/index.js.map +0 -1
- package/dist/routines/bootstrap.js +0 -30
- package/dist/routines/bootstrap.js.flow +0 -25
- package/dist/routines/bootstrap.js.map +0 -1
- package/dist/routines/index.js +0 -16
- package/dist/routines/index.js.flow +0 -3
- package/dist/routines/index.js.map +0 -1
- package/dist/types.js +0 -10
- package/dist/types.js.flow +0 -66
- package/dist/types.js.map +0 -1
- package/dist/utilities/bindHttpMethod.js +0 -62
- package/dist/utilities/bindHttpMethod.js.flow +0 -54
- package/dist/utilities/bindHttpMethod.js.map +0 -1
- package/dist/utilities/index.js +0 -32
- package/dist/utilities/index.js.flow +0 -5
- package/dist/utilities/index.js.map +0 -1
- package/dist/utilities/isUrlMatchingNoProxy.js +0 -43
- package/dist/utilities/isUrlMatchingNoProxy.js.map +0 -1
- package/dist/utilities/parseProxyUrl.js +0 -42
- package/dist/utilities/parseProxyUrl.js.flow +0 -36
- package/dist/utilities/parseProxyUrl.js.map +0 -1
- package/src/Logger.js +0 -10
- package/src/classes/Agent.js +0 -212
- package/src/classes/HttpProxyAgent.js +0 -30
- package/src/classes/HttpsProxyAgent.js +0 -54
- package/src/classes/index.js +0 -5
- package/src/factories/index.js +0 -4
- package/src/index.js +0 -4
- package/src/routines/bootstrap.js +0 -25
- package/src/routines/index.js +0 -3
- package/src/types.js +0 -66
- package/src/utilities/index.js +0 -5
- package/src/utilities/isUrlMatchingNoProxy.js +0 -37
|
@@ -1,66 +1,156 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type * as http from 'http';
|
|
2
|
+
import type * as https from 'https';
|
|
3
|
+
import net from 'net';
|
|
3
4
|
import {
|
|
4
5
|
serializeError,
|
|
5
6
|
} from 'serialize-error';
|
|
6
7
|
import {
|
|
7
|
-
|
|
8
|
-
} from '
|
|
9
|
-
import Logger from '../Logger';
|
|
8
|
+
logger,
|
|
9
|
+
} from '../Logger';
|
|
10
10
|
import type {
|
|
11
11
|
AgentType,
|
|
12
|
+
ConnectionCallbackType,
|
|
13
|
+
ConnectionConfigurationType,
|
|
12
14
|
GetUrlProxyMethodType,
|
|
13
15
|
IsProxyConfiguredMethodType,
|
|
14
16
|
MustUrlUseProxyMethodType,
|
|
15
17
|
ProtocolType,
|
|
16
18
|
} from '../types';
|
|
17
19
|
|
|
18
|
-
const log =
|
|
20
|
+
const log = logger.child({
|
|
19
21
|
namespace: 'Agent',
|
|
20
22
|
});
|
|
21
23
|
|
|
22
24
|
let requestId = 0;
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
type AgentRequestOptions = {
|
|
27
|
+
host?: string,
|
|
28
|
+
path?: string,
|
|
29
|
+
port: number,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type HttpRequestOptions = AgentRequestOptions & Omit<http.RequestOptions, keyof AgentRequestOptions> & {
|
|
33
|
+
secureEndpoint: false,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type HttpsRequestOptions = AgentRequestOptions & Omit<https.RequestOptions, keyof AgentRequestOptions> & {
|
|
37
|
+
secureEndpoint: true,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type RequestOptions = HttpRequestOptions | HttpsRequestOptions;
|
|
26
41
|
|
|
27
|
-
|
|
42
|
+
abstract class Agent {
|
|
43
|
+
public defaultPort: number;
|
|
28
44
|
|
|
29
|
-
|
|
45
|
+
public protocol: ProtocolType;
|
|
30
46
|
|
|
31
|
-
|
|
47
|
+
public fallbackAgent: AgentType;
|
|
32
48
|
|
|
33
|
-
|
|
49
|
+
public isProxyConfigured: IsProxyConfiguredMethodType;
|
|
34
50
|
|
|
35
|
-
|
|
51
|
+
public mustUrlUseProxy: MustUrlUseProxyMethodType;
|
|
36
52
|
|
|
37
|
-
|
|
53
|
+
public getUrlProxy: GetUrlProxyMethodType;
|
|
38
54
|
|
|
39
|
-
|
|
55
|
+
public socketConnectionTimeout: number;
|
|
56
|
+
|
|
57
|
+
public ca: string[] | string | undefined;
|
|
58
|
+
|
|
59
|
+
public constructor (
|
|
40
60
|
isProxyConfigured: IsProxyConfiguredMethodType,
|
|
41
61
|
mustUrlUseProxy: MustUrlUseProxyMethodType,
|
|
42
62
|
getUrlProxy: GetUrlProxyMethodType,
|
|
43
63
|
fallbackAgent: AgentType,
|
|
44
64
|
socketConnectionTimeout: number,
|
|
65
|
+
ca: string[] | string | undefined,
|
|
45
66
|
) {
|
|
46
67
|
this.fallbackAgent = fallbackAgent;
|
|
47
68
|
this.isProxyConfigured = isProxyConfigured;
|
|
48
69
|
this.mustUrlUseProxy = mustUrlUseProxy;
|
|
49
70
|
this.getUrlProxy = getUrlProxy;
|
|
50
71
|
this.socketConnectionTimeout = socketConnectionTimeout;
|
|
72
|
+
this.ca = ca;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* This method can be used to append new ca certificates to existing ca certificates
|
|
77
|
+
*
|
|
78
|
+
* @param {string[] | string} ca a ca certificate or an array of ca certificates
|
|
79
|
+
*/
|
|
80
|
+
public addCACertificates (ca: string[] | string) {
|
|
81
|
+
if (!ca) {
|
|
82
|
+
log.error('Invalid input ca certificate');
|
|
83
|
+
} else if (this.ca) {
|
|
84
|
+
if (typeof ca === typeof this.ca) {
|
|
85
|
+
// concat valid ca certificates with the existing certificates,
|
|
86
|
+
if (typeof this.ca === 'string') {
|
|
87
|
+
this.ca = this.ca.concat(ca as string);
|
|
88
|
+
} else {
|
|
89
|
+
this.ca = this.ca.concat(ca as string[]);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
log.error('Input ca certificate type mismatched with existing ca certificate type');
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
this.ca = ca;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* This method clears existing CA Certificates.
|
|
101
|
+
* It sets ca to undefined
|
|
102
|
+
*/
|
|
103
|
+
public clearCACertificates () {
|
|
104
|
+
this.ca = undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Evaluate value for tls reject unauthorized variable
|
|
109
|
+
*/
|
|
110
|
+
public getRejectUnauthorized () {
|
|
111
|
+
// eslint-disable-next-line node/no-process-env
|
|
112
|
+
const rejectUnauthorized = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
|
|
113
|
+
let returnValue = true;
|
|
114
|
+
if (typeof rejectUnauthorized === 'boolean') {
|
|
115
|
+
returnValue = rejectUnauthorized;
|
|
116
|
+
} else if (typeof rejectUnauthorized === 'number') {
|
|
117
|
+
returnValue = rejectUnauthorized === 1;
|
|
118
|
+
} else if (typeof rejectUnauthorized === 'string') {
|
|
119
|
+
returnValue = ['true', 't', 'yes', 'y', 'on', '1'].includes(rejectUnauthorized.trim().toLowerCase());
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return returnValue;
|
|
51
123
|
}
|
|
52
124
|
|
|
53
|
-
|
|
125
|
+
public abstract createConnection (configuration: ConnectionConfigurationType, callback: ConnectionCallbackType): void;
|
|
126
|
+
|
|
127
|
+
public addRequest (request: http.ClientRequest, configuration: RequestOptions) {
|
|
54
128
|
let requestUrl;
|
|
55
129
|
|
|
56
130
|
// It is possible that addRequest was constructed for a proxied request already, e.g.
|
|
57
131
|
// "request" package does this when it detects that a proxy should be used
|
|
58
132
|
// https://github.com/request/request/blob/212570b6971a732b8dd9f3c73354bcdda158a737/request.js#L402
|
|
59
133
|
// https://gist.github.com/gajus/e2074cd3b747864ffeaabbd530d30218
|
|
60
|
-
if (request.path.startsWith('http://')
|
|
134
|
+
if (request.path.startsWith('http://') ?? request.path.startsWith('https://')) {
|
|
61
135
|
requestUrl = request.path;
|
|
136
|
+
} else if (request.method === 'CONNECT') {
|
|
137
|
+
requestUrl = 'https://' + request.path;
|
|
62
138
|
} else {
|
|
63
|
-
requestUrl = this.protocol + '//' + (configuration.hostname
|
|
139
|
+
requestUrl = this.protocol + '//' + (configuration.hostname ?? configuration.host) + (configuration.port === 80 ?? configuration.port === 443 ? '' : ':' + configuration.port) + request.path;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// If a request should go to a local socket, proxying it through an HTTP
|
|
143
|
+
// server does not make sense as the information about the target socket
|
|
144
|
+
// will be lost and the proxy won't be able to correctly handle the request.
|
|
145
|
+
if (configuration.socketPath) {
|
|
146
|
+
log.trace({
|
|
147
|
+
destination: configuration.socketPath,
|
|
148
|
+
}, 'not proxying request; destination is a socket');
|
|
149
|
+
|
|
150
|
+
// @ts-expect-error seems like we are using wrong type for fallbackAgent.
|
|
151
|
+
this.fallbackAgent.addRequest(request, configuration);
|
|
152
|
+
|
|
153
|
+
return;
|
|
64
154
|
}
|
|
65
155
|
|
|
66
156
|
if (!this.isProxyConfigured()) {
|
|
@@ -68,7 +158,7 @@ class Agent {
|
|
|
68
158
|
destination: requestUrl,
|
|
69
159
|
}, 'not proxying request; GLOBAL_AGENT.HTTP_PROXY is not configured');
|
|
70
160
|
|
|
71
|
-
//
|
|
161
|
+
// @ts-expect-error seems like we are using wrong type for fallbackAgent.
|
|
72
162
|
this.fallbackAgent.addRequest(request, configuration);
|
|
73
163
|
|
|
74
164
|
return;
|
|
@@ -79,7 +169,7 @@ class Agent {
|
|
|
79
169
|
destination: requestUrl,
|
|
80
170
|
}, 'not proxying request; url matches GLOBAL_AGENT.NO_PROXY');
|
|
81
171
|
|
|
82
|
-
//
|
|
172
|
+
// @ts-expect-error seems like we are using wrong type for fallbackAgent.
|
|
83
173
|
this.fallbackAgent.addRequest(request, configuration);
|
|
84
174
|
|
|
85
175
|
return;
|
|
@@ -103,13 +193,13 @@ class Agent {
|
|
|
103
193
|
requestId: currentRequestId,
|
|
104
194
|
}, 'proxying request');
|
|
105
195
|
|
|
106
|
-
request.on('error', (error) => {
|
|
196
|
+
request.on('error', (error: Error) => {
|
|
107
197
|
log.error({
|
|
108
198
|
error: serializeError(error),
|
|
109
199
|
}, 'request error');
|
|
110
200
|
});
|
|
111
201
|
|
|
112
|
-
request.once('response', (response) => {
|
|
202
|
+
request.once('response', (response: http.IncomingMessage) => {
|
|
113
203
|
log.trace({
|
|
114
204
|
headers: response.headers,
|
|
115
205
|
requestId: currentRequestId,
|
|
@@ -120,8 +210,8 @@ class Agent {
|
|
|
120
210
|
request.shouldKeepAlive = false;
|
|
121
211
|
|
|
122
212
|
const connectionConfiguration = {
|
|
123
|
-
host: configuration.hostname
|
|
124
|
-
port: configuration.port
|
|
213
|
+
host: configuration.hostname ?? configuration.host ?? '',
|
|
214
|
+
port: configuration.port ?? 80,
|
|
125
215
|
proxy,
|
|
126
216
|
tls: {},
|
|
127
217
|
};
|
|
@@ -133,9 +223,13 @@ class Agent {
|
|
|
133
223
|
// > are also accepted:
|
|
134
224
|
// > ca, cert, ciphers, clientCertEngine, crl, dhparam, ecdhCurve, honorCipherOrder,
|
|
135
225
|
// > key, passphrase, pfx, rejectUnauthorized, secureOptions, secureProtocol, servername, sessionIdContext.
|
|
136
|
-
if (
|
|
226
|
+
if (configuration.secureEndpoint) {
|
|
227
|
+
// Determine servername - Node.js doesn't allow IP addresses as servername
|
|
228
|
+
const host = configuration.servername ?? connectionConfiguration.host;
|
|
229
|
+
const servername = net.isIP(host) ? undefined : host;
|
|
230
|
+
|
|
137
231
|
connectionConfiguration.tls = {
|
|
138
|
-
ca: configuration.ca,
|
|
232
|
+
ca: configuration.ca ?? this.ca,
|
|
139
233
|
cert: configuration.cert,
|
|
140
234
|
ciphers: configuration.ciphers,
|
|
141
235
|
clientCertEngine: configuration.clientCertEngine,
|
|
@@ -146,24 +240,14 @@ class Agent {
|
|
|
146
240
|
key: configuration.key,
|
|
147
241
|
passphrase: configuration.passphrase,
|
|
148
242
|
pfx: configuration.pfx,
|
|
149
|
-
rejectUnauthorized: configuration.rejectUnauthorized,
|
|
243
|
+
rejectUnauthorized: configuration.rejectUnauthorized ?? this.getRejectUnauthorized(),
|
|
150
244
|
secureOptions: configuration.secureOptions,
|
|
151
245
|
secureProtocol: configuration.secureProtocol,
|
|
152
|
-
servername
|
|
246
|
+
servername,
|
|
153
247
|
sessionIdContext: configuration.sessionIdContext,
|
|
154
248
|
};
|
|
155
|
-
|
|
156
|
-
// This is not ideal because there is no way to override this setting using `tls` configuration if `NODE_TLS_REJECT_UNAUTHORIZED=0`.
|
|
157
|
-
// However, popular HTTP clients (such as https://github.com/sindresorhus/got) come with pre-configured value for `rejectUnauthorized`,
|
|
158
|
-
// which makes it impossible to override that value globally and respect `rejectUnauthorized` for specific requests only.
|
|
159
|
-
//
|
|
160
|
-
// eslint-disable-next-line no-process-env
|
|
161
|
-
if (typeof process.env.NODE_TLS_REJECT_UNAUTHORIZED === 'string' && boolean(process.env.NODE_TLS_REJECT_UNAUTHORIZED) === false) {
|
|
162
|
-
connectionConfiguration.tls.rejectUnauthorized = false;
|
|
163
|
-
}
|
|
164
249
|
}
|
|
165
250
|
|
|
166
|
-
// $FlowFixMe It appears that Flow is missing the method description.
|
|
167
251
|
this.createConnection(connectionConfiguration, (error, socket) => {
|
|
168
252
|
log.trace({
|
|
169
253
|
target: connectionConfiguration,
|
|
@@ -194,10 +278,10 @@ class Agent {
|
|
|
194
278
|
|
|
195
279
|
if (error) {
|
|
196
280
|
request.emit('error', error);
|
|
197
|
-
} else {
|
|
281
|
+
} else if (socket) {
|
|
198
282
|
log.debug('created socket');
|
|
199
283
|
|
|
200
|
-
socket.on('error', (socketError) => {
|
|
284
|
+
socket.on('error', (socketError: Error) => {
|
|
201
285
|
log.error({
|
|
202
286
|
error: serializeError(socketError),
|
|
203
287
|
}, 'socket error');
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import net from 'net';
|
|
2
|
+
import type {
|
|
3
|
+
AgentType,
|
|
4
|
+
ConnectionCallbackType,
|
|
5
|
+
ConnectionConfigurationType,
|
|
6
|
+
GetUrlProxyMethodType,
|
|
7
|
+
IsProxyConfiguredMethodType,
|
|
8
|
+
MustUrlUseProxyMethodType,
|
|
9
|
+
} from '../types';
|
|
10
|
+
import Agent from './Agent';
|
|
11
|
+
|
|
12
|
+
class HttpProxyAgent extends Agent {
|
|
13
|
+
// @see https://github.com/sindresorhus/eslint-plugin-unicorn/issues/169#issuecomment-486980290
|
|
14
|
+
public constructor (
|
|
15
|
+
isProxyConfigured: IsProxyConfiguredMethodType,
|
|
16
|
+
mustUrlUseProxy: MustUrlUseProxyMethodType,
|
|
17
|
+
getUrlProxy: GetUrlProxyMethodType,
|
|
18
|
+
fallbackAgent: AgentType,
|
|
19
|
+
socketConnectionTimeout: number,
|
|
20
|
+
ca: string[] | string | undefined,
|
|
21
|
+
) {
|
|
22
|
+
super(
|
|
23
|
+
isProxyConfigured,
|
|
24
|
+
mustUrlUseProxy,
|
|
25
|
+
getUrlProxy,
|
|
26
|
+
fallbackAgent,
|
|
27
|
+
socketConnectionTimeout,
|
|
28
|
+
ca,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
this.protocol = 'http:';
|
|
32
|
+
this.defaultPort = 80;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public createConnection (configuration: ConnectionConfigurationType, callback: ConnectionCallbackType) {
|
|
36
|
+
const socket = net.connect(
|
|
37
|
+
configuration.proxy.port,
|
|
38
|
+
configuration.proxy.hostname,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
callback(null, socket);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default HttpProxyAgent;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import net from 'net';
|
|
2
|
+
import tls from 'tls';
|
|
3
|
+
import type {
|
|
4
|
+
AgentType,
|
|
5
|
+
ConnectionCallbackType,
|
|
6
|
+
ConnectionConfigurationType,
|
|
7
|
+
GetUrlProxyMethodType,
|
|
8
|
+
IsProxyConfiguredMethodType,
|
|
9
|
+
MustUrlUseProxyMethodType,
|
|
10
|
+
} from '../types';
|
|
11
|
+
import Agent from './Agent';
|
|
12
|
+
|
|
13
|
+
class HttpsProxyAgent extends Agent {
|
|
14
|
+
public constructor (
|
|
15
|
+
isProxyConfigured: IsProxyConfiguredMethodType,
|
|
16
|
+
mustUrlUseProxy: MustUrlUseProxyMethodType,
|
|
17
|
+
getUrlProxy: GetUrlProxyMethodType,
|
|
18
|
+
fallbackAgent: AgentType,
|
|
19
|
+
socketConnectionTimeout: number,
|
|
20
|
+
ca: string[] | string | undefined,
|
|
21
|
+
) {
|
|
22
|
+
super(
|
|
23
|
+
isProxyConfigured,
|
|
24
|
+
mustUrlUseProxy,
|
|
25
|
+
getUrlProxy,
|
|
26
|
+
fallbackAgent,
|
|
27
|
+
socketConnectionTimeout,
|
|
28
|
+
ca,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
this.protocol = 'https:';
|
|
32
|
+
this.defaultPort = 443;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public createConnection (configuration: ConnectionConfigurationType, callback: ConnectionCallbackType) {
|
|
36
|
+
const socket = net.connect(
|
|
37
|
+
configuration.proxy.port,
|
|
38
|
+
configuration.proxy.hostname,
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
socket.on('error', (error) => {
|
|
42
|
+
callback(error);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
socket.once('data', (data) => {
|
|
46
|
+
// Proxies with HTTPS as protocal are not allowed by parseProxyUrl(), so it should be safe to assume that the response is plain text
|
|
47
|
+
const statusLine = data.toString().split('\r\n')[0];
|
|
48
|
+
const statusLineExp = /^HTTP\/(\d)\.(\d) (\d{3}) ?(.*)$/;
|
|
49
|
+
|
|
50
|
+
const statusCode = statusLineExp.exec(statusLine)?.[3];
|
|
51
|
+
|
|
52
|
+
if (typeof statusCode === 'string' && Number(statusCode) >= 400) {
|
|
53
|
+
const error = new Error(`Proxy server refused connecting to '${configuration.host}:${configuration.port}' (${statusLine})`);
|
|
54
|
+
socket.destroy();
|
|
55
|
+
callback(error);
|
|
56
|
+
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const secureSocket = tls.connect({
|
|
61
|
+
...configuration.tls,
|
|
62
|
+
socket,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
callback(null, secureSocket);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
let connectMessage = '';
|
|
69
|
+
|
|
70
|
+
connectMessage += 'CONNECT ' + configuration.host + ':' + configuration.port + ' HTTP/1.1\r\n';
|
|
71
|
+
connectMessage += 'Host: ' + configuration.host + ':' + configuration.port + '\r\n';
|
|
72
|
+
|
|
73
|
+
if (configuration.proxy.authorization) {
|
|
74
|
+
connectMessage += 'Proxy-Authorization: Basic ' + Buffer.from(configuration.proxy.authorization).toString('base64') + '\r\n';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
connectMessage += '\r\n';
|
|
78
|
+
|
|
79
|
+
socket.write(connectMessage);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export default HttpsProxyAgent;
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
|
|
3
|
-
/* eslint-disable fp/no-class, fp/no-this */
|
|
4
|
-
|
|
5
1
|
import ExtendableError from 'es6-error';
|
|
6
2
|
|
|
7
3
|
export class UnexpectedStateError extends ExtendableError {
|
|
8
|
-
code: string;
|
|
4
|
+
public code: string;
|
|
9
5
|
|
|
10
|
-
constructor (message: string, code: string = 'UNEXPECTED_STATE_ERROR') {
|
|
6
|
+
public constructor (message: string, code: string = 'UNEXPECTED_STATE_ERROR') {
|
|
11
7
|
super(message);
|
|
12
8
|
|
|
13
9
|
this.code = code;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
|
|
3
1
|
import http from 'http';
|
|
4
2
|
import https from 'https';
|
|
5
3
|
import {
|
|
6
|
-
|
|
7
|
-
} from '
|
|
8
|
-
import
|
|
9
|
-
import
|
|
4
|
+
omitUndefined,
|
|
5
|
+
} from 'omit-undefined';
|
|
6
|
+
import semverGte from 'semver/functions/gte';
|
|
7
|
+
import {
|
|
8
|
+
logger,
|
|
9
|
+
setLogger,
|
|
10
|
+
} from '../Logger';
|
|
10
11
|
import {
|
|
11
12
|
HttpProxyAgent,
|
|
12
13
|
HttpsProxyAgent,
|
|
@@ -14,15 +15,18 @@ import {
|
|
|
14
15
|
import {
|
|
15
16
|
UnexpectedStateError,
|
|
16
17
|
} from '../errors';
|
|
18
|
+
import type {
|
|
19
|
+
ProxyAgentConfigurationInputType,
|
|
20
|
+
ProxyAgentConfigurationType,
|
|
21
|
+
} from '../types';
|
|
17
22
|
import {
|
|
18
23
|
bindHttpMethod,
|
|
19
24
|
isUrlMatchingNoProxy,
|
|
20
25
|
parseProxyUrl,
|
|
21
26
|
} from '../utilities';
|
|
22
|
-
import
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
} from '../types';
|
|
27
|
+
import {
|
|
28
|
+
parseBoolean,
|
|
29
|
+
} from '../utilities/parseBoolean';
|
|
26
30
|
import createProxyController from './createProxyController';
|
|
27
31
|
|
|
28
32
|
const httpGet = http.get;
|
|
@@ -30,34 +34,18 @@ const httpRequest = http.request;
|
|
|
30
34
|
const httpsGet = https.get;
|
|
31
35
|
const httpsRequest = https.request;
|
|
32
36
|
|
|
33
|
-
const log =
|
|
37
|
+
const log = logger.child({
|
|
34
38
|
namespace: 'createGlobalProxyAgent',
|
|
35
39
|
});
|
|
36
40
|
|
|
37
41
|
const defaultConfigurationInput = {
|
|
38
42
|
environmentVariableNamespace: undefined,
|
|
39
43
|
forceGlobalAgent: undefined,
|
|
40
|
-
socketConnectionTimeout:
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const omitUndefined = (subject) => {
|
|
44
|
-
const keys = Object.keys(subject);
|
|
45
|
-
|
|
46
|
-
const result = {};
|
|
47
|
-
|
|
48
|
-
for (const key of keys) {
|
|
49
|
-
const value = subject[key];
|
|
50
|
-
|
|
51
|
-
if (value !== undefined) {
|
|
52
|
-
result[key] = value;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return result;
|
|
44
|
+
socketConnectionTimeout: 60_000,
|
|
57
45
|
};
|
|
58
46
|
|
|
59
47
|
const createConfiguration = (configurationInput: ProxyAgentConfigurationInputType): ProxyAgentConfigurationType => {
|
|
60
|
-
// eslint-disable-next-line no-process-env
|
|
48
|
+
// eslint-disable-next-line node/no-process-env
|
|
61
49
|
const environment = process.env;
|
|
62
50
|
|
|
63
51
|
const defaultConfiguration = {
|
|
@@ -66,7 +54,6 @@ const createConfiguration = (configurationInput: ProxyAgentConfigurationInputTyp
|
|
|
66
54
|
socketConnectionTimeout: typeof environment.GLOBAL_AGENT_SOCKET_CONNECTION_TIMEOUT === 'string' ? Number.parseInt(environment.GLOBAL_AGENT_SOCKET_CONNECTION_TIMEOUT, 10) : defaultConfigurationInput.socketConnectionTimeout,
|
|
67
55
|
};
|
|
68
56
|
|
|
69
|
-
// $FlowFixMe
|
|
70
57
|
return {
|
|
71
58
|
...defaultConfiguration,
|
|
72
59
|
...omitUndefined(configurationInput),
|
|
@@ -76,24 +63,28 @@ const createConfiguration = (configurationInput: ProxyAgentConfigurationInputTyp
|
|
|
76
63
|
export default (configurationInput: ProxyAgentConfigurationInputType = defaultConfigurationInput) => {
|
|
77
64
|
const configuration = createConfiguration(configurationInput);
|
|
78
65
|
|
|
66
|
+
if (configurationInput.logger) {
|
|
67
|
+
setLogger(configurationInput.logger);
|
|
68
|
+
}
|
|
69
|
+
|
|
79
70
|
const proxyController = createProxyController();
|
|
80
71
|
|
|
81
|
-
// eslint-disable-next-line no-process-env
|
|
82
|
-
proxyController.HTTP_PROXY = process.env[configuration.environmentVariableNamespace + 'HTTP_PROXY']
|
|
72
|
+
// eslint-disable-next-line node/no-process-env
|
|
73
|
+
proxyController.HTTP_PROXY = process.env[configuration.environmentVariableNamespace + 'HTTP_PROXY'] ?? null;
|
|
83
74
|
|
|
84
|
-
// eslint-disable-next-line no-process-env
|
|
85
|
-
proxyController.HTTPS_PROXY = process.env[configuration.environmentVariableNamespace + 'HTTPS_PROXY']
|
|
75
|
+
// eslint-disable-next-line node/no-process-env
|
|
76
|
+
proxyController.HTTPS_PROXY = process.env[configuration.environmentVariableNamespace + 'HTTPS_PROXY'] ?? null;
|
|
86
77
|
|
|
87
|
-
// eslint-disable-next-line no-process-env
|
|
88
|
-
proxyController.NO_PROXY = process.env[configuration.environmentVariableNamespace + 'NO_PROXY']
|
|
78
|
+
// eslint-disable-next-line node/no-process-env
|
|
79
|
+
proxyController.NO_PROXY = process.env[configuration.environmentVariableNamespace + 'NO_PROXY'] ?? null;
|
|
89
80
|
|
|
90
81
|
log.info({
|
|
91
82
|
configuration,
|
|
92
83
|
state: proxyController,
|
|
93
84
|
}, 'global agent has been initialized');
|
|
94
85
|
|
|
95
|
-
const mustUrlUseProxy = (getProxy) => {
|
|
96
|
-
return (url) => {
|
|
86
|
+
const mustUrlUseProxy = (getProxy: () => string | null) => {
|
|
87
|
+
return (url: string): boolean => {
|
|
97
88
|
if (!getProxy()) {
|
|
98
89
|
return false;
|
|
99
90
|
}
|
|
@@ -106,7 +97,7 @@ export default (configurationInput: ProxyAgentConfigurationInputType = defaultCo
|
|
|
106
97
|
};
|
|
107
98
|
};
|
|
108
99
|
|
|
109
|
-
const getUrlProxy = (getProxy) => {
|
|
100
|
+
const getUrlProxy = (getProxy: () => string | null) => {
|
|
110
101
|
return () => {
|
|
111
102
|
const proxy = getProxy();
|
|
112
103
|
|
|
@@ -123,15 +114,16 @@ export default (configurationInput: ProxyAgentConfigurationInputType = defaultCo
|
|
|
123
114
|
};
|
|
124
115
|
|
|
125
116
|
const BoundHttpProxyAgent = class extends HttpProxyAgent {
|
|
126
|
-
constructor () {
|
|
117
|
+
public constructor () {
|
|
127
118
|
super(
|
|
128
119
|
() => {
|
|
129
|
-
return getHttpProxy();
|
|
120
|
+
return Boolean(getHttpProxy());
|
|
130
121
|
},
|
|
131
122
|
mustUrlUseProxy(getHttpProxy),
|
|
132
123
|
getUrlProxy(getHttpProxy),
|
|
133
124
|
http.globalAgent,
|
|
134
125
|
configuration.socketConnectionTimeout,
|
|
126
|
+
configuration.ca,
|
|
135
127
|
);
|
|
136
128
|
}
|
|
137
129
|
};
|
|
@@ -139,19 +131,20 @@ export default (configurationInput: ProxyAgentConfigurationInputType = defaultCo
|
|
|
139
131
|
const httpAgent = new BoundHttpProxyAgent();
|
|
140
132
|
|
|
141
133
|
const getHttpsProxy = () => {
|
|
142
|
-
return proxyController.HTTPS_PROXY
|
|
134
|
+
return proxyController.HTTPS_PROXY ?? proxyController.HTTP_PROXY;
|
|
143
135
|
};
|
|
144
136
|
|
|
145
137
|
const BoundHttpsProxyAgent = class extends HttpsProxyAgent {
|
|
146
|
-
constructor () {
|
|
138
|
+
public constructor () {
|
|
147
139
|
super(
|
|
148
140
|
() => {
|
|
149
|
-
return getHttpsProxy();
|
|
141
|
+
return Boolean(getHttpsProxy());
|
|
150
142
|
},
|
|
151
143
|
mustUrlUseProxy(getHttpsProxy),
|
|
152
144
|
getUrlProxy(getHttpsProxy),
|
|
153
145
|
https.globalAgent,
|
|
154
146
|
configuration.socketConnectionTimeout,
|
|
147
|
+
configuration.ca,
|
|
155
148
|
);
|
|
156
149
|
}
|
|
157
150
|
};
|
|
@@ -160,12 +153,12 @@ export default (configurationInput: ProxyAgentConfigurationInputType = defaultCo
|
|
|
160
153
|
|
|
161
154
|
// Overriding globalAgent was added in v11.7.
|
|
162
155
|
// @see https://nodejs.org/uk/blog/release/v11.7.0/
|
|
163
|
-
if (
|
|
156
|
+
if (semverGte(process.version, 'v11.7.0')) {
|
|
164
157
|
// @see https://github.com/facebook/flow/issues/7670
|
|
165
|
-
//
|
|
158
|
+
// @ts-expect-error Node.js version compatibility
|
|
166
159
|
http.globalAgent = httpAgent;
|
|
167
160
|
|
|
168
|
-
//
|
|
161
|
+
// @ts-expect-error Node.js version compatibility
|
|
169
162
|
https.globalAgent = httpsAgent;
|
|
170
163
|
}
|
|
171
164
|
|
|
@@ -177,17 +170,17 @@ export default (configurationInput: ProxyAgentConfigurationInputType = defaultCo
|
|
|
177
170
|
//
|
|
178
171
|
// We still want to override http(s).globalAgent when possible to enable logic
|
|
179
172
|
// in `bindHttpMethod`.
|
|
180
|
-
if (
|
|
181
|
-
//
|
|
173
|
+
if (semverGte(process.version, 'v10.0.0')) {
|
|
174
|
+
// @ts-expect-error seems like we are using wrong type for httpAgent
|
|
182
175
|
http.get = bindHttpMethod(httpGet, httpAgent, configuration.forceGlobalAgent);
|
|
183
176
|
|
|
184
|
-
//
|
|
177
|
+
// @ts-expect-error seems like we are using wrong type for httpAgent
|
|
185
178
|
http.request = bindHttpMethod(httpRequest, httpAgent, configuration.forceGlobalAgent);
|
|
186
179
|
|
|
187
|
-
//
|
|
180
|
+
// @ts-expect-error seems like we are using wrong type for httpAgent
|
|
188
181
|
https.get = bindHttpMethod(httpsGet, httpsAgent, configuration.forceGlobalAgent);
|
|
189
182
|
|
|
190
|
-
//
|
|
183
|
+
// @ts-expect-error seems like we are using wrong type for httpAgent
|
|
191
184
|
https.request = bindHttpMethod(httpsRequest, httpsAgent, configuration.forceGlobalAgent);
|
|
192
185
|
} else {
|
|
193
186
|
log.warn('attempt to initialize global-agent in unsupported Node.js version was ignored');
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
import {
|
|
2
|
+
logger,
|
|
3
|
+
} from '../Logger';
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
type ProxyControllerType = {|
|
|
5
|
+
type ProxyController = {
|
|
6
6
|
HTTP_PROXY: string | null,
|
|
7
7
|
HTTPS_PROXY: string | null,
|
|
8
8
|
NO_PROXY: string | null,
|
|
9
|
-
|
|
9
|
+
};
|
|
10
10
|
|
|
11
|
-
const log =
|
|
11
|
+
const log = logger.child({
|
|
12
12
|
namespace: 'createProxyController',
|
|
13
13
|
});
|
|
14
14
|
|
|
@@ -18,7 +18,7 @@ const KNOWN_PROPERTY_NAMES = [
|
|
|
18
18
|
'NO_PROXY',
|
|
19
19
|
];
|
|
20
20
|
|
|
21
|
-
export default ():
|
|
21
|
+
export default (): ProxyController => {
|
|
22
22
|
// eslint-disable-next-line fp/no-proxy
|
|
23
23
|
return new Proxy({
|
|
24
24
|
HTTP_PROXY: null,
|
|
@@ -26,10 +26,15 @@ export default (): ProxyControllerType => {
|
|
|
26
26
|
NO_PROXY: null,
|
|
27
27
|
}, {
|
|
28
28
|
set: (subject, name, value) => {
|
|
29
|
+
if (typeof name !== 'string') {
|
|
30
|
+
throw new TypeError('Unexpected object member.');
|
|
31
|
+
}
|
|
32
|
+
|
|
29
33
|
if (!KNOWN_PROPERTY_NAMES.includes(name)) {
|
|
30
34
|
throw new Error('Cannot set an unmapped property "' + name + '".');
|
|
31
35
|
}
|
|
32
36
|
|
|
37
|
+
// @ts-expect-error string cannot be used to index an object
|
|
33
38
|
subject[name] = value;
|
|
34
39
|
|
|
35
40
|
log.info({
|