Skip to content

Commit 16dc53c

Browse files
joyeecheungaduh95
authored andcommitted
test: move http proxy tests to test/client-proxy
Rewrite to ESM to use TLA. Also add a test to make sure case precedence is honored. Refs: https://about.gitlab.com/blog/we-need-to-talk-no-proxy PR-URL: #58980 Refs: #57872 Refs: #8381 Refs: #15620 Reviewed-By: Matteo Collina <[email protected]>
1 parent a9511a6 commit 16dc53c

File tree

7 files changed

+255
-136
lines changed

7 files changed

+255
-136
lines changed

test/client-proxy/client-proxy.status

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
prefix client-proxy
2+
3+
# To mark a test as flaky, list the test name in the appropriate section
4+
# below, without ".js", followed by ": PASS,FLAKY". Example:
5+
# sample-test : PASS,FLAKY
6+
7+
[true] # This section applies to all platforms
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import * as common from '../common/index.mjs';
2+
import assert from 'node:assert';
3+
import { once } from 'events';
4+
import http from 'node:http';
5+
import { createProxyServer, checkProxiedFetch } from '../common/proxy-server.js';
6+
7+
// Start a server to process the final request.
8+
const server = http.createServer(common.mustCall((req, res) => {
9+
res.end('Hello world');
10+
}, common.isWindows ? 2 : 3));
11+
server.on('error', common.mustNotCall((err) => { console.error('Server error', err); }));
12+
server.listen(0);
13+
await once(server, 'listening');
14+
15+
// Start a minimal proxy server.
16+
const { proxy, logs } = createProxyServer();
17+
proxy.listen(0);
18+
await once(proxy, 'listening');
19+
20+
const serverHost = `localhost:${server.address().port}`;
21+
22+
// FIXME(undici:4083): undici currently always tunnels the request over
23+
// CONNECT if proxyTunnel is not explicitly set to false, but what we
24+
// need is for it to be automatically false for HTTP requests to be
25+
// consistent with curl.
26+
const expectedLogs = [{
27+
method: 'CONNECT',
28+
url: serverHost,
29+
headers: {
30+
'connection': 'close',
31+
'host': serverHost,
32+
'proxy-connection': 'keep-alive',
33+
},
34+
}];
35+
36+
// Check upper-cased HTTPS_PROXY environment variable.
37+
await checkProxiedFetch({
38+
NODE_USE_ENV_PROXY: 1,
39+
FETCH_URL: `http://${serverHost}/test`,
40+
HTTP_PROXY: `http://localhost:${proxy.address().port}`,
41+
}, {
42+
stdout: 'Hello world',
43+
});
44+
assert.deepStrictEqual(logs, expectedLogs);
45+
46+
// Check lower-cased https_proxy environment variable.
47+
logs.splice(0, logs.length);
48+
await checkProxiedFetch({
49+
NODE_USE_ENV_PROXY: 1,
50+
FETCH_URL: `http://${serverHost}/test`,
51+
http_proxy: `http://localhost:${proxy.address().port}`,
52+
}, {
53+
stdout: 'Hello world',
54+
});
55+
assert.deepStrictEqual(logs, expectedLogs);
56+
57+
// Check lower-cased http_proxy environment variable takes precedence.
58+
// On Windows, environment variables are case-insensitive, so this test
59+
// is not applicable.
60+
if (!common.isWindows) {
61+
const proxy2 = http.createServer();
62+
proxy2.on('connect', common.mustNotCall());
63+
proxy2.listen(0);
64+
await once(proxy2, 'listening');
65+
66+
logs.splice(0, logs.length);
67+
await checkProxiedFetch({
68+
NODE_USE_ENV_PROXY: 1,
69+
FETCH_URL: `http://${serverHost}/test`,
70+
http_proxy: `http://localhost:${proxy.address().port}`,
71+
HTTP_PROXY: `http://localhost:${proxy2.address().port}`,
72+
}, {
73+
stdout: 'Hello world',
74+
});
75+
assert.deepStrictEqual(logs, expectedLogs);
76+
proxy2.close();
77+
}
78+
79+
proxy.close();
80+
server.close();
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import * as common from '../common/index.mjs';
2+
import fixtures from '../common/fixtures.js';
3+
import assert from 'node:assert';
4+
import http from 'node:http';
5+
import { once } from 'events';
6+
import { createProxyServer, checkProxiedFetch } from '../common/proxy-server.js';
7+
8+
if (!common.hasCrypto)
9+
common.skip('missing crypto');
10+
11+
// https must be dynamically imported so that builds without crypto support
12+
// can skip it.
13+
const https = (await import('node:https')).default;
14+
15+
// Start a server to process the final request.
16+
const server = https.createServer({
17+
cert: fixtures.readKey('agent8-cert.pem'),
18+
key: fixtures.readKey('agent8-key.pem'),
19+
}, common.mustCall((req, res) => {
20+
res.end('Hello world');
21+
}, common.isWindows ? 2 : 3));
22+
server.on('error', common.mustNotCall((err) => { console.error('Server error', err); }));
23+
server.listen(0);
24+
await once(server, 'listening');
25+
26+
// Start a minimal proxy server.
27+
const { proxy, logs } = createProxyServer();
28+
proxy.listen(0);
29+
await once(proxy, 'listening');
30+
31+
const serverHost = `localhost:${server.address().port}`;
32+
33+
const expectedLogs = [{
34+
method: 'CONNECT',
35+
url: serverHost,
36+
headers: {
37+
'connection': 'close',
38+
'host': serverHost,
39+
'proxy-connection': 'keep-alive',
40+
},
41+
}];
42+
43+
// Check upper-cased HTTPS_PROXY environment variable.
44+
await checkProxiedFetch({
45+
NODE_USE_ENV_PROXY: 1,
46+
FETCH_URL: `https://${serverHost}/test`,
47+
HTTPS_PROXY: `http://localhost:${proxy.address().port}`,
48+
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'fake-startcom-root-cert.pem'),
49+
}, {
50+
stdout: 'Hello world',
51+
});
52+
assert.deepStrictEqual(logs, expectedLogs);
53+
54+
// Check lower-cased https_proxy environment variable.
55+
logs.splice(0, logs.length);
56+
await checkProxiedFetch({
57+
NODE_USE_ENV_PROXY: 1,
58+
FETCH_URL: `https://${serverHost}/test`,
59+
https_proxy: `http://localhost:${proxy.address().port}`,
60+
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'fake-startcom-root-cert.pem'),
61+
}, {
62+
stdout: 'Hello world',
63+
});
64+
assert.deepStrictEqual(logs, expectedLogs);
65+
66+
// Check lower-cased http_proxy environment variable takes precedence.
67+
// On Windows, environment variables are case-insensitive, so this test
68+
// is not applicable.
69+
if (!common.isWindows) {
70+
const proxy2 = http.createServer();
71+
proxy2.on('connect', common.mustNotCall());
72+
proxy2.listen(0);
73+
await once(proxy2, 'listening');
74+
75+
logs.splice(0, logs.length);
76+
await checkProxiedFetch({
77+
NODE_USE_ENV_PROXY: 1,
78+
FETCH_URL: `https://${serverHost}/test`,
79+
https_proxy: `http://localhost:${proxy.address().port}`,
80+
HTTPS_PROXY: `http://localhost:${proxy2.address().port}`,
81+
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'fake-startcom-root-cert.pem'),
82+
}, {
83+
stdout: 'Hello world',
84+
});
85+
assert.deepStrictEqual(logs, expectedLogs);
86+
proxy2.close();
87+
}
88+
89+
90+
proxy.close();
91+
server.close();

test/client-proxy/testcfg.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import sys, os
2+
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
3+
import testpy
4+
5+
def GetConfiguration(context, root):
6+
return testpy.ParallelTestConfiguration(context, root, 'client-proxy')

test/common/proxy-server.js

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,32 @@ function logRequest(logs, req) {
1414

1515
// This creates a minimal proxy server that logs the requests it gets
1616
// to an array before performing proxying.
17-
exports.createProxyServer = function() {
17+
exports.createProxyServer = function(options = {}) {
1818
const logs = [];
1919

20-
const proxy = http.createServer();
20+
let proxy;
21+
if (options.https) {
22+
const common = require('../common');
23+
if (!common.hasCrypto) {
24+
common.skip('missing crypto');
25+
}
26+
proxy = require('https').createServer({
27+
cert: require('./fixtures').readKey('agent9-cert.pem'),
28+
key: require('./fixtures').readKey('agent9-key.pem'),
29+
});
30+
} else {
31+
proxy = http.createServer();
32+
}
2133
proxy.on('request', (req, res) => {
2234
logRequest(logs, req);
2335
const [hostname, port] = req.headers.host.split(':');
2436
const targetPort = port || 80;
2537

38+
const url = new URL(req.url);
2639
const options = {
2740
hostname: hostname,
2841
port: targetPort,
29-
path: req.url,
42+
path: url.pathname + url.search, // Convert back to relative URL.
3043
method: req.method,
3144
headers: req.headers,
3245
};
@@ -38,8 +51,16 @@ exports.createProxyServer = function() {
3851

3952
proxyReq.on('error', (err) => {
4053
logs.push({ error: err, source: 'proxy request' });
41-
res.writeHead(500);
42-
res.end('Proxy error: ' + err.message);
54+
if (!res.headersSent) {
55+
res.writeHead(500);
56+
}
57+
if (!res.writableEnded) {
58+
res.end(`Proxy error ${err.code}: ${err.message}`);
59+
}
60+
});
61+
62+
res.on('error', (err) => {
63+
logs.push({ error: err, source: 'proxy response' });
4364
});
4465

4566
req.pipe(proxyReq, { end: true });
@@ -49,6 +70,11 @@ exports.createProxyServer = function() {
4970
logRequest(logs, req);
5071

5172
const [hostname, port] = req.url.split(':');
73+
74+
res.on('error', (err) => {
75+
logs.push({ error: err, source: 'proxy response' });
76+
});
77+
5278
const proxyReq = net.connect(port, hostname, () => {
5379
res.write(
5480
'HTTP/1.1 200 Connection Established\r\n' +
@@ -74,8 +100,46 @@ exports.createProxyServer = function() {
74100
return { proxy, logs };
75101
};
76102

77-
exports.checkProxiedRequest = async function(envExtension, expectation) {
78-
const { spawnPromisified } = require('./');
103+
function spawnPromisified(...args) {
104+
const { spawn } = require('child_process');
105+
let stderr = '';
106+
let stdout = '';
107+
108+
const child = spawn(...args);
109+
child.stderr.setEncoding('utf8');
110+
child.stderr.on('data', (data) => {
111+
console.error('[STDERR]', data);
112+
stderr += data;
113+
});
114+
child.stdout.setEncoding('utf8');
115+
child.stdout.on('data', (data) => {
116+
console.log('[STDOUT]', data);
117+
stdout += data;
118+
});
119+
120+
return new Promise((resolve, reject) => {
121+
child.on('close', (code, signal) => {
122+
console.log('[CLOSE]', code, signal);
123+
resolve({
124+
code,
125+
signal,
126+
stderr,
127+
stdout,
128+
});
129+
});
130+
child.on('error', (code, signal) => {
131+
console.log('[ERROR]', code, signal);
132+
reject({
133+
code,
134+
signal,
135+
stderr,
136+
stdout,
137+
});
138+
});
139+
});
140+
}
141+
142+
exports.checkProxiedFetch = async function(envExtension, expectation) {
79143
const fixtures = require('./fixtures');
80144
const { code, signal, stdout, stderr } = await spawnPromisified(
81145
process.execPath,

test/parallel/test-http-proxy-fetch.js

Lines changed: 0 additions & 62 deletions
This file was deleted.

0 commit comments

Comments
 (0)