Skip to content

Commit b282d6a

Browse files
committed
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
1 parent 8173d9d commit b282d6a

File tree

7 files changed

+243
-136
lines changed

7 files changed

+243
-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: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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+
}, 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+
const proxy2 = http.createServer();
58+
proxy2.on('connect', common.mustNotCall());
59+
proxy2.listen(0);
60+
await once(proxy2, 'listening');
61+
62+
// Check lower-cased http_proxy environment variable takes precedence.
63+
logs.splice(0, logs.length);
64+
await checkProxiedFetch({
65+
NODE_USE_ENV_PROXY: 1,
66+
FETCH_URL: `http://${serverHost}/test`,
67+
http_proxy: `http://localhost:${proxy.address().port}`,
68+
HTTP_PROXY: `http://localhost:${proxy2.address().port}`,
69+
}, {
70+
stdout: 'Hello world',
71+
});
72+
assert.deepStrictEqual(logs, expectedLogs);
73+
74+
proxy.close();
75+
proxy2.close();
76+
server.close();
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import * as common from '../common/index.mjs';
2+
import fixtures from '../common/fixtures.js';
3+
import assert from 'node:assert';
4+
import https from 'node:https';
5+
import http from 'node:http';
6+
import { once } from 'events';
7+
import { createProxyServer, checkProxiedFetch } from '../common/proxy-server.js';
8+
9+
if (!common.hasCrypto)
10+
common.skip('missing crypto');
11+
12+
// Start a server to process the final request.
13+
const server = https.createServer({
14+
cert: fixtures.readKey('agent8-cert.pem'),
15+
key: fixtures.readKey('agent8-key.pem'),
16+
}, common.mustCall((req, res) => {
17+
res.end('Hello world');
18+
}, 3));
19+
server.on('error', common.mustNotCall((err) => { console.error('Server error', err); }));
20+
server.listen(0);
21+
await once(server, 'listening');
22+
23+
// Start a minimal proxy server.
24+
const { proxy, logs } = createProxyServer();
25+
proxy.listen(0);
26+
await once(proxy, 'listening');
27+
28+
const serverHost = `localhost:${server.address().port}`;
29+
30+
const expectedLogs = [{
31+
method: 'CONNECT',
32+
url: serverHost,
33+
headers: {
34+
'connection': 'close',
35+
'host': serverHost,
36+
'proxy-connection': 'keep-alive',
37+
},
38+
}];
39+
40+
// Check upper-cased HTTPS_PROXY environment variable.
41+
await checkProxiedFetch({
42+
NODE_USE_ENV_PROXY: 1,
43+
FETCH_URL: `https://${serverHost}/test`,
44+
HTTPS_PROXY: `http://localhost:${proxy.address().port}`,
45+
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'fake-startcom-root-cert.pem'),
46+
}, {
47+
stdout: 'Hello world',
48+
});
49+
assert.deepStrictEqual(logs, expectedLogs);
50+
51+
// Check lower-cased https_proxy environment variable.
52+
logs.splice(0, logs.length);
53+
await checkProxiedFetch({
54+
NODE_USE_ENV_PROXY: 1,
55+
FETCH_URL: `https://${serverHost}/test`,
56+
https_proxy: `http://localhost:${proxy.address().port}`,
57+
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'fake-startcom-root-cert.pem'),
58+
}, {
59+
stdout: 'Hello world',
60+
});
61+
assert.deepStrictEqual(logs, expectedLogs);
62+
63+
const proxy2 = http.createServer();
64+
proxy2.on('connect', common.mustNotCall());
65+
proxy2.listen(0);
66+
await once(proxy2, 'listening');
67+
68+
// Check lower-cased https_proxy environment variable takes precedence.
69+
logs.splice(0, logs.length);
70+
await checkProxiedFetch({
71+
NODE_USE_ENV_PROXY: 1,
72+
FETCH_URL: `https://${serverHost}/test`,
73+
https_proxy: `http://localhost:${proxy.address().port}`,
74+
HTTPS_PROXY: `http://localhost:${proxy2.address().port}`,
75+
NODE_EXTRA_CA_CERTS: fixtures.path('keys', 'fake-startcom-root-cert.pem'),
76+
}, {
77+
stdout: 'Hello world',
78+
});
79+
assert.deepStrictEqual(logs, expectedLogs);
80+
81+
proxy.close();
82+
proxy2.close();
83+
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)