| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent a9511a6 commit 16dc53c
7 files changed
| Original file line number | Diff line number | Diff 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 | ||
| Original file line number | Diff line number | Diff 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(); | ||
| Original file line number | Diff line number | Diff 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(); | ||
| Original file line number | Diff line number | Diff 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') | ||
| 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, | |
| Back | FazBrowse Home | New Git URL |
0 commit comments