| 'use strict'; |
| |
| const net = require('net'); |
| const http = require('http'); |
| const assert = require('assert'); |
| |
| function logRequest(logs, req) { |
| logs.push({ |
| method: req.method, |
| url: req.url, |
| headers: { ...req.headers }, |
| }); |
| } |
| |
| // This creates a minimal proxy server that logs the requests it gets |
| // to an array before performing proxying. |
| exports.createProxyServer = function(options = {}) { |
| const logs = []; |
| |
| let proxy; |
| if (options.https) { |
| const common = require('../common'); |
| if (!common.hasCrypto) { |
| common.skip('missing crypto'); |
| } |
| proxy = require('https').createServer({ |
| cert: require('./fixtures').readKey('agent9-cert.pem'), |
| key: require('./fixtures').readKey('agent9-key.pem'), |
| }); |
| } else { |
| proxy = http.createServer(); |
| } |
| proxy.on('request', (req, res) => { |
| logRequest(logs, req); |
| const { hostname, port } = new URL(`http://${req.headers.host}`); |
| const targetPort = port || 80; |
| |
| const url = new URL(req.url); |
| const options = { |
| hostname: hostname.startsWith('[') ? hostname.slice(1, -1) : hostname, |
| port: targetPort, |
| path: url.pathname + url.search, // Convert back to relative URL. |
| method: req.method, |
| headers: { |
| ...req.headers, |
| 'connection': req.headers['proxy-connection'] || 'close', |
| }, |
| }; |
| |
| const proxyReq = http.request(options, (proxyRes) => { |
| res.writeHead(proxyRes.statusCode, proxyRes.headers); |
| proxyRes.pipe(res, { end: true }); |
| }); |
| |
| proxyReq.on('error', (err) => { |
| logs.push({ error: err, source: 'proxy request' }); |
| if (!res.headersSent) { |
| res.writeHead(500); |
| } |
| if (!res.writableEnded) { |
| res.end(`Proxy error ${err.code}: ${err.message}`); |
| } |
| }); |
| |
| res.on('error', (err) => { |
| logs.push({ error: err, source: 'client response for request' }); |
| }); |
| |
| req.pipe(proxyReq, { end: true }); |
| }); |
| |
| proxy.on('connect', (req, res, head) => { |
| logRequest(logs, req); |
| |
| const { hostname, port } = new URL(`https://${req.url}`); |
| |
| res.on('error', (err) => { |
| logs.push({ error: err, source: 'client response for connect' }); |
| }); |
| |
| const normalizedHostname = hostname.startsWith('[') && hostname.endsWith(']') ? |
| hostname.slice(1, -1) : hostname; |
| const proxyReq = net.connect(port, normalizedHostname, () => { |
| res.write( |
| 'HTTP/1.1 200 Connection Established\r\n' + |
| 'Proxy-agent: Node.js-Proxy\r\n' + |
| '\r\n', |
| ); |
| proxyReq.write(head); |
| res.pipe(proxyReq); |
| proxyReq.pipe(res); |
| }); |
| |
| proxyReq.on('error', (err) => { |
| logs.push({ error: err, source: 'proxy connect' }); |
| // The proxy client might have already closed the connection |
| // when the upstream connection fails. |
| if (!res.writableEnded) { |
| res.write('HTTP/1.1 500 Connection Error\r\n\r\n'); |
| res.end('Proxy error: ' + err.message); |
| } |
| }); |
| }); |
| |
| proxy.on('error', (err) => { |
| logs.push({ error: err, source: 'proxy server' }); |
| }); |
| |
| return { proxy, logs }; |
| }; |
| |
| function spawnPromisified(...args) { |
| const { spawn } = require('child_process'); |
| let stderr = ''; |
| let stdout = ''; |
| |
| const child = spawn(...args); |
| child.stderr.setEncoding('utf8'); |
| child.stderr.on('data', (data) => { |
| console.error('[STDERR]', data); |
| stderr += data; |
| }); |
| child.stdout.setEncoding('utf8'); |
| child.stdout.on('data', (data) => { |
| console.log('[STDOUT]', data); |
| stdout += data; |
| }); |
| |
| return new Promise((resolve, reject) => { |
| child.on('close', (code, signal) => { |
| console.log('[CLOSE]', code, signal); |
| resolve({ |
| code, |
| signal, |
| stderr, |
| stdout, |
| }); |
| }); |
| child.on('error', (code, signal) => { |
| console.log('[ERROR]', code, signal); |
| reject({ |
| code, |
| signal, |
| stderr, |
| stdout, |
| }); |
| }); |
| }); |
| } |
| |
| exports.checkProxiedFetch = async function(envExtension, expectation, cliArgsExtension = []) { |
| const fixtures = require('./fixtures'); |
| const { code, signal, stdout, stderr } = await spawnPromisified( |
| process.execPath, |
| [...cliArgsExtension, fixtures.path('fetch-and-log.mjs')], { |
| env: { |
| ...process.env, |
| ...envExtension, |
| }, |
| }); |
| |
| assert.deepStrictEqual({ |
| stderr: stderr.trim(), |
| stdout: stdout.trim(), |
| code, |
| signal, |
| }, { |
| stderr: '', |
| code: 0, |
| signal: null, |
| ...expectation, |
| }); |
| }; |
| |
| exports.runProxiedRequest = async function(envExtension, cliArgsExtension = []) { |
| const fixtures = require('./fixtures'); |
| return spawnPromisified( |
| process.execPath, |
| [...cliArgsExtension, fixtures.path('request-and-log.js')], { |
| env: { |
| ...process.env, |
| ...envExtension, |
| }, |
| }); |
| }; |
| |
| exports.runProxiedPOST = async function(envExtension) { |
| const fixtures = require('./fixtures'); |
| return spawnPromisified( |
| process.execPath, |
| [fixtures.path('post-resource-and-log.js')], { |
| env: { |
| ...process.env, |
| ...envExtension, |
| }, |
| }); |
| }; |