@tramvai/module-http-proxy-agent 2.70.1 → 2.72.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/lib/add-proxy-to-https-agent/add-proxy-to-https-agent.es.js +103 -0
- package/lib/add-proxy-to-https-agent/add-proxy-to-https-agent.js +112 -0
- package/lib/add-proxy-to-https-agent/match-no-proxy.es.js +31 -0
- package/lib/add-proxy-to-https-agent/match-no-proxy.js +35 -0
- package/lib/add-proxy-to-https-agent/parse-proxy.es.js +9 -0
- package/lib/add-proxy-to-https-agent/parse-proxy.js +17 -0
- package/lib/server.es.js +2 -148
- package/lib/server.js +6 -158
- package/lib/utils/env.es.js +14 -0
- package/lib/utils/env.js +20 -0
- package/package.json +7 -8
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import https from 'https';
|
|
2
|
+
import net from 'net';
|
|
3
|
+
import { matchNoProxy } from './match-no-proxy.es.js';
|
|
4
|
+
import { getHttpsProxy, getNoProxy } from '../utils/env.es.js';
|
|
5
|
+
import { parseProxy } from './parse-proxy.es.js';
|
|
6
|
+
|
|
7
|
+
/* eslint-disable no-param-reassign */
|
|
8
|
+
/**
|
|
9
|
+
* Fork of https://github.com/mknj/node-keepalive-proxy-agent with monkeypatching and no_proxy support
|
|
10
|
+
*/
|
|
11
|
+
const addProxyToHttpsAgent = ({ logger, metrics, }) => {
|
|
12
|
+
const httpsProxyEnv = getHttpsProxy();
|
|
13
|
+
const noProxyEnv = getNoProxy();
|
|
14
|
+
const noProxyMatchResults = {};
|
|
15
|
+
const metricsConnectionCounter = metrics.counter({
|
|
16
|
+
name: 'http_proxy_connect_total',
|
|
17
|
+
help: 'Number of proxy connects',
|
|
18
|
+
labelNames: ['host'],
|
|
19
|
+
});
|
|
20
|
+
if (!httpsProxyEnv) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const proxy = parseProxy(httpsProxyEnv);
|
|
24
|
+
logger.debug({
|
|
25
|
+
event: 'parsed proxy',
|
|
26
|
+
proxy,
|
|
27
|
+
});
|
|
28
|
+
// @ts-expect-error
|
|
29
|
+
const originalCreateConnection = https.Agent.prototype.createConnection;
|
|
30
|
+
// @ts-expect-error
|
|
31
|
+
https.Agent.prototype.createConnection = createConnection;
|
|
32
|
+
function createConnection(options, cb) {
|
|
33
|
+
const { hostname, href, method, headers } = options;
|
|
34
|
+
const connectionMustBeProxied = !noProxyEnv || !matchNoProxyWithCache({ hostname });
|
|
35
|
+
if (connectionMustBeProxied) {
|
|
36
|
+
logger.debug({
|
|
37
|
+
event: 'proxy connection',
|
|
38
|
+
connection: {
|
|
39
|
+
href,
|
|
40
|
+
method,
|
|
41
|
+
headers,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
createConnectionHttpsAfterHttp.call(this, options, cb);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
cb(null, originalCreateConnection.call(this, options));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function createConnectionHttpsAfterHttp(options, cb) {
|
|
51
|
+
const proxySocket = net.connect(+proxy.port, proxy.hostname);
|
|
52
|
+
const host = options.hostname || options.host;
|
|
53
|
+
const errorListener = (error) => {
|
|
54
|
+
proxySocket.destroy();
|
|
55
|
+
cb(error);
|
|
56
|
+
};
|
|
57
|
+
const successConnectionListener = () => {
|
|
58
|
+
metricsConnectionCounter.inc({ host });
|
|
59
|
+
};
|
|
60
|
+
proxySocket.once('error', errorListener);
|
|
61
|
+
proxySocket.on('connect', successConnectionListener);
|
|
62
|
+
let response = '';
|
|
63
|
+
const dataListener = (data) => {
|
|
64
|
+
response += data.toString();
|
|
65
|
+
if (!response.endsWith('\r\n\r\n')) {
|
|
66
|
+
// response not completed yet
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
proxySocket.removeListener('error', errorListener);
|
|
70
|
+
proxySocket.removeListener('data', dataListener);
|
|
71
|
+
const m = response.match(/^HTTP\/1.\d (\d*)/);
|
|
72
|
+
if (m == null || m[1] == null) {
|
|
73
|
+
proxySocket.destroy();
|
|
74
|
+
return cb(new Error(response.trim()));
|
|
75
|
+
}
|
|
76
|
+
if (m[1] !== '200') {
|
|
77
|
+
proxySocket.destroy();
|
|
78
|
+
return cb(new Error(m[0]));
|
|
79
|
+
}
|
|
80
|
+
// tell super function to use our proxy socket
|
|
81
|
+
options.socket = proxySocket;
|
|
82
|
+
cb(null, originalCreateConnection.call(this, options));
|
|
83
|
+
};
|
|
84
|
+
proxySocket.on('data', dataListener);
|
|
85
|
+
// https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.6
|
|
86
|
+
let cmd = `CONNECT ${host}:${options.port} HTTP/1.1\r\n`;
|
|
87
|
+
cmd += `Host: ${host}:${options.port}\r\n`;
|
|
88
|
+
cmd += '\r\n';
|
|
89
|
+
proxySocket.write(cmd);
|
|
90
|
+
}
|
|
91
|
+
function matchNoProxyWithCache({ hostname }) {
|
|
92
|
+
if (hostname in noProxyMatchResults) {
|
|
93
|
+
return noProxyMatchResults[hostname];
|
|
94
|
+
}
|
|
95
|
+
const noProxy = noProxyEnv;
|
|
96
|
+
const result = matchNoProxy({ noProxy, hostname });
|
|
97
|
+
noProxyMatchResults[hostname] = result;
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
/* eslint-enable no-param-reassign */
|
|
102
|
+
|
|
103
|
+
export { addProxyToHttpsAgent };
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var https = require('https');
|
|
6
|
+
var net = require('net');
|
|
7
|
+
var matchNoProxy = require('./match-no-proxy.js');
|
|
8
|
+
var env = require('../utils/env.js');
|
|
9
|
+
var parseProxy = require('./parse-proxy.js');
|
|
10
|
+
|
|
11
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
12
|
+
|
|
13
|
+
var https__default = /*#__PURE__*/_interopDefaultLegacy(https);
|
|
14
|
+
var net__default = /*#__PURE__*/_interopDefaultLegacy(net);
|
|
15
|
+
|
|
16
|
+
/* eslint-disable no-param-reassign */
|
|
17
|
+
/**
|
|
18
|
+
* Fork of https://github.com/mknj/node-keepalive-proxy-agent with monkeypatching and no_proxy support
|
|
19
|
+
*/
|
|
20
|
+
const addProxyToHttpsAgent = ({ logger, metrics, }) => {
|
|
21
|
+
const httpsProxyEnv = env.getHttpsProxy();
|
|
22
|
+
const noProxyEnv = env.getNoProxy();
|
|
23
|
+
const noProxyMatchResults = {};
|
|
24
|
+
const metricsConnectionCounter = metrics.counter({
|
|
25
|
+
name: 'http_proxy_connect_total',
|
|
26
|
+
help: 'Number of proxy connects',
|
|
27
|
+
labelNames: ['host'],
|
|
28
|
+
});
|
|
29
|
+
if (!httpsProxyEnv) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const proxy = parseProxy.parseProxy(httpsProxyEnv);
|
|
33
|
+
logger.debug({
|
|
34
|
+
event: 'parsed proxy',
|
|
35
|
+
proxy,
|
|
36
|
+
});
|
|
37
|
+
// @ts-expect-error
|
|
38
|
+
const originalCreateConnection = https__default["default"].Agent.prototype.createConnection;
|
|
39
|
+
// @ts-expect-error
|
|
40
|
+
https__default["default"].Agent.prototype.createConnection = createConnection;
|
|
41
|
+
function createConnection(options, cb) {
|
|
42
|
+
const { hostname, href, method, headers } = options;
|
|
43
|
+
const connectionMustBeProxied = !noProxyEnv || !matchNoProxyWithCache({ hostname });
|
|
44
|
+
if (connectionMustBeProxied) {
|
|
45
|
+
logger.debug({
|
|
46
|
+
event: 'proxy connection',
|
|
47
|
+
connection: {
|
|
48
|
+
href,
|
|
49
|
+
method,
|
|
50
|
+
headers,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
createConnectionHttpsAfterHttp.call(this, options, cb);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
cb(null, originalCreateConnection.call(this, options));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function createConnectionHttpsAfterHttp(options, cb) {
|
|
60
|
+
const proxySocket = net__default["default"].connect(+proxy.port, proxy.hostname);
|
|
61
|
+
const host = options.hostname || options.host;
|
|
62
|
+
const errorListener = (error) => {
|
|
63
|
+
proxySocket.destroy();
|
|
64
|
+
cb(error);
|
|
65
|
+
};
|
|
66
|
+
const successConnectionListener = () => {
|
|
67
|
+
metricsConnectionCounter.inc({ host });
|
|
68
|
+
};
|
|
69
|
+
proxySocket.once('error', errorListener);
|
|
70
|
+
proxySocket.on('connect', successConnectionListener);
|
|
71
|
+
let response = '';
|
|
72
|
+
const dataListener = (data) => {
|
|
73
|
+
response += data.toString();
|
|
74
|
+
if (!response.endsWith('\r\n\r\n')) {
|
|
75
|
+
// response not completed yet
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
proxySocket.removeListener('error', errorListener);
|
|
79
|
+
proxySocket.removeListener('data', dataListener);
|
|
80
|
+
const m = response.match(/^HTTP\/1.\d (\d*)/);
|
|
81
|
+
if (m == null || m[1] == null) {
|
|
82
|
+
proxySocket.destroy();
|
|
83
|
+
return cb(new Error(response.trim()));
|
|
84
|
+
}
|
|
85
|
+
if (m[1] !== '200') {
|
|
86
|
+
proxySocket.destroy();
|
|
87
|
+
return cb(new Error(m[0]));
|
|
88
|
+
}
|
|
89
|
+
// tell super function to use our proxy socket
|
|
90
|
+
options.socket = proxySocket;
|
|
91
|
+
cb(null, originalCreateConnection.call(this, options));
|
|
92
|
+
};
|
|
93
|
+
proxySocket.on('data', dataListener);
|
|
94
|
+
// https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.6
|
|
95
|
+
let cmd = `CONNECT ${host}:${options.port} HTTP/1.1\r\n`;
|
|
96
|
+
cmd += `Host: ${host}:${options.port}\r\n`;
|
|
97
|
+
cmd += '\r\n';
|
|
98
|
+
proxySocket.write(cmd);
|
|
99
|
+
}
|
|
100
|
+
function matchNoProxyWithCache({ hostname }) {
|
|
101
|
+
if (hostname in noProxyMatchResults) {
|
|
102
|
+
return noProxyMatchResults[hostname];
|
|
103
|
+
}
|
|
104
|
+
const noProxy = noProxyEnv;
|
|
105
|
+
const result = matchNoProxy.matchNoProxy({ noProxy, hostname });
|
|
106
|
+
noProxyMatchResults[hostname] = result;
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
/* eslint-enable no-param-reassign */
|
|
111
|
+
|
|
112
|
+
exports.addProxyToHttpsAgent = addProxyToHttpsAgent;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const RULES_SEPARATOR = ',';
|
|
2
|
+
const HOST_PORT_SEPARATOR = ':';
|
|
3
|
+
const WILDCARD = '*';
|
|
4
|
+
/**
|
|
5
|
+
* Support specification proposal from https://about.gitlab.com/blog/2021/01/27/we-need-to-talk-no-proxy/#the-lowest-common-denominator
|
|
6
|
+
*/
|
|
7
|
+
const matchNoProxy = ({ noProxy, hostname, }) => {
|
|
8
|
+
const rules = noProxy.split(RULES_SEPARATOR).filter(Boolean);
|
|
9
|
+
for (const rule of rules) {
|
|
10
|
+
// @todo: respect port when matching
|
|
11
|
+
const [ruleHost, _rulePort] = rule.split(HOST_PORT_SEPARATOR);
|
|
12
|
+
// Wildcard (*) match all hosts
|
|
13
|
+
if (ruleHost === WILDCARD) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
// Strip leading dots (.) and wildcard with dot (*.) for backward compatibility
|
|
17
|
+
const ruleHostWithoutLeadingDot = ruleHost.replace(/^\*?\./, '');
|
|
18
|
+
const matchRegex = new RegExp(`${ruleHostWithoutLeadingDot}$`);
|
|
19
|
+
/**
|
|
20
|
+
* @example
|
|
21
|
+
* 'localhost'.match(/localhost$/)
|
|
22
|
+
* 'api.test.com'.match(/test.com$/)
|
|
23
|
+
*/
|
|
24
|
+
if (hostname.match(matchRegex)) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export { matchNoProxy };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
const RULES_SEPARATOR = ',';
|
|
6
|
+
const HOST_PORT_SEPARATOR = ':';
|
|
7
|
+
const WILDCARD = '*';
|
|
8
|
+
/**
|
|
9
|
+
* Support specification proposal from https://about.gitlab.com/blog/2021/01/27/we-need-to-talk-no-proxy/#the-lowest-common-denominator
|
|
10
|
+
*/
|
|
11
|
+
const matchNoProxy = ({ noProxy, hostname, }) => {
|
|
12
|
+
const rules = noProxy.split(RULES_SEPARATOR).filter(Boolean);
|
|
13
|
+
for (const rule of rules) {
|
|
14
|
+
// @todo: respect port when matching
|
|
15
|
+
const [ruleHost, _rulePort] = rule.split(HOST_PORT_SEPARATOR);
|
|
16
|
+
// Wildcard (*) match all hosts
|
|
17
|
+
if (ruleHost === WILDCARD) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
// Strip leading dots (.) and wildcard with dot (*.) for backward compatibility
|
|
21
|
+
const ruleHostWithoutLeadingDot = ruleHost.replace(/^\*?\./, '');
|
|
22
|
+
const matchRegex = new RegExp(`${ruleHostWithoutLeadingDot}$`);
|
|
23
|
+
/**
|
|
24
|
+
* @example
|
|
25
|
+
* 'localhost'.match(/localhost$/)
|
|
26
|
+
* 'api.test.com'.match(/test.com$/)
|
|
27
|
+
*/
|
|
28
|
+
if (hostname.match(matchRegex)) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
exports.matchNoProxy = matchNoProxy;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var url = require('url');
|
|
6
|
+
|
|
7
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
8
|
+
|
|
9
|
+
var url__default = /*#__PURE__*/_interopDefaultLegacy(url);
|
|
10
|
+
|
|
11
|
+
function parseProxy(httpsProxyEnv) {
|
|
12
|
+
const parsedProxy = new url__default["default"].URL(httpsProxyEnv);
|
|
13
|
+
const proxy = { hostname: parsedProxy.hostname, port: parsedProxy.port || '80' };
|
|
14
|
+
return proxy;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
exports.parseProxy = parseProxy;
|
package/lib/server.es.js
CHANGED
|
@@ -2,154 +2,8 @@ import { __decorate } from 'tslib';
|
|
|
2
2
|
import { Module, commandLineListTokens } from '@tramvai/core';
|
|
3
3
|
import { LOGGER_TOKEN } from '@tramvai/tokens-common';
|
|
4
4
|
import { METRICS_MODULE_TOKEN } from '@tramvai/tokens-metrics';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import url from 'url';
|
|
8
|
-
|
|
9
|
-
const httpProxyEnabled = () => {
|
|
10
|
-
const { http_proxy, HTTP_PROXY, https_proxy, HTTPS_PROXY, no_proxy, NO_PROXY } = process.env;
|
|
11
|
-
return !!(http_proxy || HTTP_PROXY || https_proxy || HTTPS_PROXY || no_proxy || NO_PROXY);
|
|
12
|
-
};
|
|
13
|
-
const getHttpsProxy = () => {
|
|
14
|
-
const { https_proxy, HTTPS_PROXY } = process.env;
|
|
15
|
-
return https_proxy || HTTPS_PROXY || '';
|
|
16
|
-
};
|
|
17
|
-
const getNoProxy = () => {
|
|
18
|
-
const { no_proxy, NO_PROXY } = process.env;
|
|
19
|
-
return no_proxy || NO_PROXY || '';
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const RULES_SEPARATOR = ',';
|
|
23
|
-
const HOST_PORT_SEPARATOR = ':';
|
|
24
|
-
const WILDCARD = '*';
|
|
25
|
-
/**
|
|
26
|
-
* Support specification proposal from https://about.gitlab.com/blog/2021/01/27/we-need-to-talk-no-proxy/#the-lowest-common-denominator
|
|
27
|
-
*/
|
|
28
|
-
const matchNoProxy = ({ noProxy, hostname, }) => {
|
|
29
|
-
const rules = noProxy.split(RULES_SEPARATOR).filter(Boolean);
|
|
30
|
-
for (const rule of rules) {
|
|
31
|
-
// @todo: respect port when matching
|
|
32
|
-
const [ruleHost, _rulePort] = rule.split(HOST_PORT_SEPARATOR);
|
|
33
|
-
// Wildcard (*) match all hosts
|
|
34
|
-
if (ruleHost === WILDCARD) {
|
|
35
|
-
return true;
|
|
36
|
-
}
|
|
37
|
-
// Strip leading dots (.) and wildcard with dot (*.) for backward compatibility
|
|
38
|
-
const ruleHostWithoutLeadingDot = ruleHost.replace(/^\*?\./, '');
|
|
39
|
-
const matchRegex = new RegExp(`${ruleHostWithoutLeadingDot}$`);
|
|
40
|
-
/**
|
|
41
|
-
* @example
|
|
42
|
-
* 'localhost'.match(/localhost$/)
|
|
43
|
-
* 'api.test.com'.match(/test.com$/)
|
|
44
|
-
*/
|
|
45
|
-
if (hostname.match(matchRegex)) {
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return false;
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
function parseProxy(httpsProxyEnv) {
|
|
53
|
-
const parsedProxy = new url.URL(httpsProxyEnv);
|
|
54
|
-
const proxy = { hostname: parsedProxy.hostname, port: parsedProxy.port || '80' };
|
|
55
|
-
return proxy;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/* eslint-disable no-param-reassign */
|
|
59
|
-
/**
|
|
60
|
-
* Fork of https://github.com/mknj/node-keepalive-proxy-agent with monkeypatching and no_proxy support
|
|
61
|
-
*/
|
|
62
|
-
const addProxyToHttpsAgent = ({ logger, metrics, }) => {
|
|
63
|
-
const httpsProxyEnv = getHttpsProxy();
|
|
64
|
-
const noProxyEnv = getNoProxy();
|
|
65
|
-
const noProxyMatchResults = {};
|
|
66
|
-
const metricsConnectionCounter = metrics.counter({
|
|
67
|
-
name: 'http_proxy_connect_total',
|
|
68
|
-
help: 'Number of proxy connects',
|
|
69
|
-
labelNames: ['host'],
|
|
70
|
-
});
|
|
71
|
-
if (!httpsProxyEnv) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
const proxy = parseProxy(httpsProxyEnv);
|
|
75
|
-
logger.debug({
|
|
76
|
-
event: 'parsed proxy',
|
|
77
|
-
proxy,
|
|
78
|
-
});
|
|
79
|
-
// @ts-expect-error
|
|
80
|
-
const originalCreateConnection = https.Agent.prototype.createConnection;
|
|
81
|
-
// @ts-expect-error
|
|
82
|
-
https.Agent.prototype.createConnection = createConnection;
|
|
83
|
-
function createConnection(options, cb) {
|
|
84
|
-
const { hostname, href, method, headers } = options;
|
|
85
|
-
const connectionMustBeProxied = !noProxyEnv || !matchNoProxyWithCache({ hostname });
|
|
86
|
-
if (connectionMustBeProxied) {
|
|
87
|
-
logger.debug({
|
|
88
|
-
event: 'proxy connection',
|
|
89
|
-
connection: {
|
|
90
|
-
href,
|
|
91
|
-
method,
|
|
92
|
-
headers,
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
createConnectionHttpsAfterHttp.call(this, options, cb);
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
cb(null, originalCreateConnection.call(this, options));
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
function createConnectionHttpsAfterHttp(options, cb) {
|
|
102
|
-
const proxySocket = net.connect(+proxy.port, proxy.hostname);
|
|
103
|
-
const host = options.hostname || options.host;
|
|
104
|
-
const errorListener = (error) => {
|
|
105
|
-
proxySocket.destroy();
|
|
106
|
-
cb(error);
|
|
107
|
-
};
|
|
108
|
-
const successConnectionListener = () => {
|
|
109
|
-
metricsConnectionCounter.inc({ host });
|
|
110
|
-
};
|
|
111
|
-
proxySocket.once('error', errorListener);
|
|
112
|
-
proxySocket.on('connect', successConnectionListener);
|
|
113
|
-
let response = '';
|
|
114
|
-
const dataListener = (data) => {
|
|
115
|
-
response += data.toString();
|
|
116
|
-
if (!response.endsWith('\r\n\r\n')) {
|
|
117
|
-
// response not completed yet
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
proxySocket.removeListener('error', errorListener);
|
|
121
|
-
proxySocket.removeListener('data', dataListener);
|
|
122
|
-
const m = response.match(/^HTTP\/1.\d (\d*)/);
|
|
123
|
-
if (m == null || m[1] == null) {
|
|
124
|
-
proxySocket.destroy();
|
|
125
|
-
return cb(new Error(response.trim()));
|
|
126
|
-
}
|
|
127
|
-
if (m[1] !== '200') {
|
|
128
|
-
proxySocket.destroy();
|
|
129
|
-
return cb(new Error(m[0]));
|
|
130
|
-
}
|
|
131
|
-
// tell super function to use our proxy socket
|
|
132
|
-
options.socket = proxySocket;
|
|
133
|
-
cb(null, originalCreateConnection.call(this, options));
|
|
134
|
-
};
|
|
135
|
-
proxySocket.on('data', dataListener);
|
|
136
|
-
// https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.6
|
|
137
|
-
let cmd = `CONNECT ${host}:${options.port} HTTP/1.1\r\n`;
|
|
138
|
-
cmd += `Host: ${host}:${options.port}\r\n`;
|
|
139
|
-
cmd += '\r\n';
|
|
140
|
-
proxySocket.write(cmd);
|
|
141
|
-
}
|
|
142
|
-
function matchNoProxyWithCache({ hostname }) {
|
|
143
|
-
if (hostname in noProxyMatchResults) {
|
|
144
|
-
return noProxyMatchResults[hostname];
|
|
145
|
-
}
|
|
146
|
-
const noProxy = noProxyEnv;
|
|
147
|
-
const result = matchNoProxy({ noProxy, hostname });
|
|
148
|
-
noProxyMatchResults[hostname] = result;
|
|
149
|
-
return result;
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
/* eslint-enable no-param-reassign */
|
|
5
|
+
import { httpProxyEnabled, getHttpsProxy, getNoProxy } from './utils/env.es.js';
|
|
6
|
+
import { addProxyToHttpsAgent } from './add-proxy-to-https-agent/add-proxy-to-https-agent.es.js';
|
|
153
7
|
|
|
154
8
|
let HttpProxyAgentModule = class HttpProxyAgentModule {
|
|
155
9
|
};
|
package/lib/server.js
CHANGED
|
@@ -6,160 +6,8 @@ var tslib = require('tslib');
|
|
|
6
6
|
var core = require('@tramvai/core');
|
|
7
7
|
var tokensCommon = require('@tramvai/tokens-common');
|
|
8
8
|
var tokensMetrics = require('@tramvai/tokens-metrics');
|
|
9
|
-
var
|
|
10
|
-
var
|
|
11
|
-
var url = require('url');
|
|
12
|
-
|
|
13
|
-
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
14
|
-
|
|
15
|
-
var https__default = /*#__PURE__*/_interopDefaultLegacy(https);
|
|
16
|
-
var net__default = /*#__PURE__*/_interopDefaultLegacy(net);
|
|
17
|
-
var url__default = /*#__PURE__*/_interopDefaultLegacy(url);
|
|
18
|
-
|
|
19
|
-
const httpProxyEnabled = () => {
|
|
20
|
-
const { http_proxy, HTTP_PROXY, https_proxy, HTTPS_PROXY, no_proxy, NO_PROXY } = process.env;
|
|
21
|
-
return !!(http_proxy || HTTP_PROXY || https_proxy || HTTPS_PROXY || no_proxy || NO_PROXY);
|
|
22
|
-
};
|
|
23
|
-
const getHttpsProxy = () => {
|
|
24
|
-
const { https_proxy, HTTPS_PROXY } = process.env;
|
|
25
|
-
return https_proxy || HTTPS_PROXY || '';
|
|
26
|
-
};
|
|
27
|
-
const getNoProxy = () => {
|
|
28
|
-
const { no_proxy, NO_PROXY } = process.env;
|
|
29
|
-
return no_proxy || NO_PROXY || '';
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const RULES_SEPARATOR = ',';
|
|
33
|
-
const HOST_PORT_SEPARATOR = ':';
|
|
34
|
-
const WILDCARD = '*';
|
|
35
|
-
/**
|
|
36
|
-
* Support specification proposal from https://about.gitlab.com/blog/2021/01/27/we-need-to-talk-no-proxy/#the-lowest-common-denominator
|
|
37
|
-
*/
|
|
38
|
-
const matchNoProxy = ({ noProxy, hostname, }) => {
|
|
39
|
-
const rules = noProxy.split(RULES_SEPARATOR).filter(Boolean);
|
|
40
|
-
for (const rule of rules) {
|
|
41
|
-
// @todo: respect port when matching
|
|
42
|
-
const [ruleHost, _rulePort] = rule.split(HOST_PORT_SEPARATOR);
|
|
43
|
-
// Wildcard (*) match all hosts
|
|
44
|
-
if (ruleHost === WILDCARD) {
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
// Strip leading dots (.) and wildcard with dot (*.) for backward compatibility
|
|
48
|
-
const ruleHostWithoutLeadingDot = ruleHost.replace(/^\*?\./, '');
|
|
49
|
-
const matchRegex = new RegExp(`${ruleHostWithoutLeadingDot}$`);
|
|
50
|
-
/**
|
|
51
|
-
* @example
|
|
52
|
-
* 'localhost'.match(/localhost$/)
|
|
53
|
-
* 'api.test.com'.match(/test.com$/)
|
|
54
|
-
*/
|
|
55
|
-
if (hostname.match(matchRegex)) {
|
|
56
|
-
return true;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return false;
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
function parseProxy(httpsProxyEnv) {
|
|
63
|
-
const parsedProxy = new url__default["default"].URL(httpsProxyEnv);
|
|
64
|
-
const proxy = { hostname: parsedProxy.hostname, port: parsedProxy.port || '80' };
|
|
65
|
-
return proxy;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/* eslint-disable no-param-reassign */
|
|
69
|
-
/**
|
|
70
|
-
* Fork of https://github.com/mknj/node-keepalive-proxy-agent with monkeypatching and no_proxy support
|
|
71
|
-
*/
|
|
72
|
-
const addProxyToHttpsAgent = ({ logger, metrics, }) => {
|
|
73
|
-
const httpsProxyEnv = getHttpsProxy();
|
|
74
|
-
const noProxyEnv = getNoProxy();
|
|
75
|
-
const noProxyMatchResults = {};
|
|
76
|
-
const metricsConnectionCounter = metrics.counter({
|
|
77
|
-
name: 'http_proxy_connect_total',
|
|
78
|
-
help: 'Number of proxy connects',
|
|
79
|
-
labelNames: ['host'],
|
|
80
|
-
});
|
|
81
|
-
if (!httpsProxyEnv) {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const proxy = parseProxy(httpsProxyEnv);
|
|
85
|
-
logger.debug({
|
|
86
|
-
event: 'parsed proxy',
|
|
87
|
-
proxy,
|
|
88
|
-
});
|
|
89
|
-
// @ts-expect-error
|
|
90
|
-
const originalCreateConnection = https__default["default"].Agent.prototype.createConnection;
|
|
91
|
-
// @ts-expect-error
|
|
92
|
-
https__default["default"].Agent.prototype.createConnection = createConnection;
|
|
93
|
-
function createConnection(options, cb) {
|
|
94
|
-
const { hostname, href, method, headers } = options;
|
|
95
|
-
const connectionMustBeProxied = !noProxyEnv || !matchNoProxyWithCache({ hostname });
|
|
96
|
-
if (connectionMustBeProxied) {
|
|
97
|
-
logger.debug({
|
|
98
|
-
event: 'proxy connection',
|
|
99
|
-
connection: {
|
|
100
|
-
href,
|
|
101
|
-
method,
|
|
102
|
-
headers,
|
|
103
|
-
},
|
|
104
|
-
});
|
|
105
|
-
createConnectionHttpsAfterHttp.call(this, options, cb);
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
cb(null, originalCreateConnection.call(this, options));
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
function createConnectionHttpsAfterHttp(options, cb) {
|
|
112
|
-
const proxySocket = net__default["default"].connect(+proxy.port, proxy.hostname);
|
|
113
|
-
const host = options.hostname || options.host;
|
|
114
|
-
const errorListener = (error) => {
|
|
115
|
-
proxySocket.destroy();
|
|
116
|
-
cb(error);
|
|
117
|
-
};
|
|
118
|
-
const successConnectionListener = () => {
|
|
119
|
-
metricsConnectionCounter.inc({ host });
|
|
120
|
-
};
|
|
121
|
-
proxySocket.once('error', errorListener);
|
|
122
|
-
proxySocket.on('connect', successConnectionListener);
|
|
123
|
-
let response = '';
|
|
124
|
-
const dataListener = (data) => {
|
|
125
|
-
response += data.toString();
|
|
126
|
-
if (!response.endsWith('\r\n\r\n')) {
|
|
127
|
-
// response not completed yet
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
proxySocket.removeListener('error', errorListener);
|
|
131
|
-
proxySocket.removeListener('data', dataListener);
|
|
132
|
-
const m = response.match(/^HTTP\/1.\d (\d*)/);
|
|
133
|
-
if (m == null || m[1] == null) {
|
|
134
|
-
proxySocket.destroy();
|
|
135
|
-
return cb(new Error(response.trim()));
|
|
136
|
-
}
|
|
137
|
-
if (m[1] !== '200') {
|
|
138
|
-
proxySocket.destroy();
|
|
139
|
-
return cb(new Error(m[0]));
|
|
140
|
-
}
|
|
141
|
-
// tell super function to use our proxy socket
|
|
142
|
-
options.socket = proxySocket;
|
|
143
|
-
cb(null, originalCreateConnection.call(this, options));
|
|
144
|
-
};
|
|
145
|
-
proxySocket.on('data', dataListener);
|
|
146
|
-
// https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.6
|
|
147
|
-
let cmd = `CONNECT ${host}:${options.port} HTTP/1.1\r\n`;
|
|
148
|
-
cmd += `Host: ${host}:${options.port}\r\n`;
|
|
149
|
-
cmd += '\r\n';
|
|
150
|
-
proxySocket.write(cmd);
|
|
151
|
-
}
|
|
152
|
-
function matchNoProxyWithCache({ hostname }) {
|
|
153
|
-
if (hostname in noProxyMatchResults) {
|
|
154
|
-
return noProxyMatchResults[hostname];
|
|
155
|
-
}
|
|
156
|
-
const noProxy = noProxyEnv;
|
|
157
|
-
const result = matchNoProxy({ noProxy, hostname });
|
|
158
|
-
noProxyMatchResults[hostname] = result;
|
|
159
|
-
return result;
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
/* eslint-enable no-param-reassign */
|
|
9
|
+
var env = require('./utils/env.js');
|
|
10
|
+
var addProxyToHttpsAgent = require('./add-proxy-to-https-agent/add-proxy-to-https-agent.js');
|
|
163
11
|
|
|
164
12
|
exports.HttpProxyAgentModule = class HttpProxyAgentModule {
|
|
165
13
|
};
|
|
@@ -167,17 +15,17 @@ exports.HttpProxyAgentModule = tslib.__decorate([
|
|
|
167
15
|
core.Module({
|
|
168
16
|
imports: [],
|
|
169
17
|
providers: [
|
|
170
|
-
httpProxyEnabled() && {
|
|
18
|
+
env.httpProxyEnabled() && {
|
|
171
19
|
provide: core.commandLineListTokens.init,
|
|
172
20
|
multi: true,
|
|
173
21
|
useFactory: ({ loggerFactory, metrics }) => function addHttpsProxy() {
|
|
174
22
|
const logger = loggerFactory('http-proxy-agent');
|
|
175
23
|
logger.debug({
|
|
176
24
|
event: 'proxy agent enabled',
|
|
177
|
-
proxyEnv: getHttpsProxy(),
|
|
178
|
-
noProxyEnv: getNoProxy(),
|
|
25
|
+
proxyEnv: env.getHttpsProxy(),
|
|
26
|
+
noProxyEnv: env.getNoProxy(),
|
|
179
27
|
});
|
|
180
|
-
addProxyToHttpsAgent({ logger, metrics });
|
|
28
|
+
addProxyToHttpsAgent.addProxyToHttpsAgent({ logger, metrics });
|
|
181
29
|
},
|
|
182
30
|
deps: {
|
|
183
31
|
loggerFactory: tokensCommon.LOGGER_TOKEN,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const httpProxyEnabled = () => {
|
|
2
|
+
const { http_proxy, HTTP_PROXY, https_proxy, HTTPS_PROXY, no_proxy, NO_PROXY } = process.env;
|
|
3
|
+
return !!(http_proxy || HTTP_PROXY || https_proxy || HTTPS_PROXY || no_proxy || NO_PROXY);
|
|
4
|
+
};
|
|
5
|
+
const getHttpsProxy = () => {
|
|
6
|
+
const { https_proxy, HTTPS_PROXY } = process.env;
|
|
7
|
+
return https_proxy || HTTPS_PROXY || '';
|
|
8
|
+
};
|
|
9
|
+
const getNoProxy = () => {
|
|
10
|
+
const { no_proxy, NO_PROXY } = process.env;
|
|
11
|
+
return no_proxy || NO_PROXY || '';
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export { getHttpsProxy, getNoProxy, httpProxyEnabled };
|
package/lib/utils/env.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
const httpProxyEnabled = () => {
|
|
6
|
+
const { http_proxy, HTTP_PROXY, https_proxy, HTTPS_PROXY, no_proxy, NO_PROXY } = process.env;
|
|
7
|
+
return !!(http_proxy || HTTP_PROXY || https_proxy || HTTPS_PROXY || no_proxy || NO_PROXY);
|
|
8
|
+
};
|
|
9
|
+
const getHttpsProxy = () => {
|
|
10
|
+
const { https_proxy, HTTPS_PROXY } = process.env;
|
|
11
|
+
return https_proxy || HTTPS_PROXY || '';
|
|
12
|
+
};
|
|
13
|
+
const getNoProxy = () => {
|
|
14
|
+
const { no_proxy, NO_PROXY } = process.env;
|
|
15
|
+
return no_proxy || NO_PROXY || '';
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
exports.getHttpsProxy = getHttpsProxy;
|
|
19
|
+
exports.getNoProxy = getNoProxy;
|
|
20
|
+
exports.httpProxyEnabled = httpProxyEnabled;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tramvai/module-http-proxy-agent",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.72.0",
|
|
4
4
|
"description": "Enable support for http_proxy, https_proxy and no_proxy env variables",
|
|
5
5
|
"browser": "lib/browser.js",
|
|
6
6
|
"main": "lib/server.js",
|
|
@@ -16,21 +16,20 @@
|
|
|
16
16
|
},
|
|
17
17
|
"license": "Apache-2.0",
|
|
18
18
|
"scripts": {
|
|
19
|
-
"build": "tramvai-build --
|
|
20
|
-
"watch": "tsc -w"
|
|
21
|
-
"build-for-publish": "true"
|
|
19
|
+
"build": "tramvai-build --forPublish --preserveModules",
|
|
20
|
+
"watch": "tsc -w"
|
|
22
21
|
},
|
|
23
22
|
"publishConfig": {
|
|
24
23
|
"registry": "https://registry.npmjs.org/"
|
|
25
24
|
},
|
|
26
25
|
"dependencies": {
|
|
27
|
-
"@tramvai/tokens-common": "2.
|
|
26
|
+
"@tramvai/tokens-common": "2.72.0"
|
|
28
27
|
},
|
|
29
28
|
"devDependencies": {},
|
|
30
29
|
"peerDependencies": {
|
|
31
|
-
"@tramvai/core": "2.
|
|
32
|
-
"@tinkoff/dippy": "0.8.
|
|
33
|
-
"@tramvai/tokens-metrics": "2.
|
|
30
|
+
"@tramvai/core": "2.72.0",
|
|
31
|
+
"@tinkoff/dippy": "0.8.13",
|
|
32
|
+
"@tramvai/tokens-metrics": "2.72.0",
|
|
34
33
|
"tslib": "^2.4.0"
|
|
35
34
|
}
|
|
36
35
|
}
|