GeezerVison is showing github.com — test: move http proxy tests to test/client-proxy · nodejs/node@ebec3ef
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboard +7Lines changed: 7 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 |
Collapse file Copy file name to clipboard +80Lines changed: 80 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | + // FIXME(undici:4086): this should be keep-alive. | | 31 | + connection: 'close', | | 32 | + host: serverHost, | | 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(); |
Collapse file Copy file name to clipboard +91Lines changed: 91 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | + // FIXME(undici:4086): this should be keep-alive. | | 38 | + connection: 'close', | | 39 | + host: serverHost, | | 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(); |
Collapse file Copy file name to clipboard +6Lines changed: 6 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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') |
Collapse file Copy file name to clipboardExpand all lines: test/common/proxy-server.js +71-7Lines changed: 71 additions & 7 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -14,19 +14,32 @@ function logRequest(logs, req) { | 14 | 14 |
| 15 | 15 | // This creates a minimal proxy server that logs the requests it gets | 16 | 16 | // to an array before performing proxying. | 17 | | -exports.createProxyServer = function() { | | 17 | +exports.createProxyServer = function(options = {}) { | 18 | 18 | const logs = []; | 19 | 19 |
| 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 | + } | 21 | 33 | proxy.on('request', (req, res) => { | 22 | 34 | logRequest(logs, req); | 23 | 35 | const [hostname, port] = req.headers.host.split(':'); | 24 | 36 | const targetPort = port || 80; | 25 | 37 |
| | 38 | + const url = new URL(req.url); | 26 | 39 | const options = { | 27 | 40 | hostname: hostname, | 28 | 41 | port: targetPort, | 29 | | - path: req.url, | | 42 | + path: url.pathname + url.search, // Convert back to relative URL. | 30 | 43 | method: req.method, | 31 | 44 | headers: req.headers, | 32 | 45 | }; | @@ -38,8 +51,16 @@ exports.createProxyServer = function() { | 38 | 51 |
| 39 | 52 | proxyReq.on('error', (err) => { | 40 | 53 | 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' }); | 43 | 64 | }); | 44 | 65 |
| 45 | 66 | req.pipe(proxyReq, { end: true }); | @@ -49,6 +70,11 @@ exports.createProxyServer = function() { | 49 | 70 | logRequest(logs, req); | 50 | 71 |
| 51 | 72 | const [hostname, port] = req.url.split(':'); | | 73 | + | | 74 | + res.on('error', (err) => { | | 75 | + logs.push({ error: err, source: 'proxy response' }); | | 76 | + }); | | 77 | + | 52 | 78 | const proxyReq = net.connect(port, hostname, () => { | 53 | 79 | res.write( | 54 | 80 | 'HTTP/1.1 200 Connection Established\r\n' + | @@ -74,8 +100,46 @@ exports.createProxyServer = function() { | 74 | 100 | return { proxy, logs }; | 75 | 101 | }; | 76 | 102 |
| 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) { | 79 | 143 | const fixtures = require('./fixtures'); | 80 | 144 | const { code, signal, stdout, stderr } = await spawnPromisified( | 81 | 145 | process.execPath, | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-http-proxy-fetch.js -61Lines changed: 0 additions & 61 deletions
|