const http = require('http');
const net = require('net');
const { URL } = require('url');
const { once } = require('events');
/**
* A simple HTTP proxy agent implementation
* This is a simplified version for demonstration purposes
* In production, consider using the 'https-proxy-agent' or 'http-proxy-agent' packages
*/
class HttpProxyAgent extends http.Agent {
constructor(proxyUrl, options = {}) {
super(options);
this.proxyUrl = new URL(proxyUrl);
this.secureProxy = this.proxyUrl.protocol === 'https:' || this.proxyUrl.protocol === 'https:';
// Default options
this.defaultPort = this.secureProxy ? 443 : 80;
this.proxyAuth = this.proxyUrl.username && this.proxyUrl.password
? `Basic ${Buffer.from(`${this.proxyUrl.username}:${this.proxyUrl.password}`).toString('base64')}`
: null;
}
createConnection(options, callback) {
const proxyOptions = {
host: this.proxyUrl.hostname,
port: this.proxyUrl.port || (this.secureProxy ? 443 : 80),
path: `${options.host}:${options.port}`,
method: 'CONNECT',
headers: {
'Host': `${options.host}:${options.port}`
}
};
// Add proxy authentication if provided
if (this.proxyAuth) {
proxyOptions.headers['Proxy-Authorization'] = this.proxyAuth;
}
// Create a connection to the proxy server
const proxySocket = net.connect(proxyOptions);
// Handle connection errors
proxySocket.on('error', (err) => {
callback(err);
});
// Handle successful connection to the proxy
proxySocket.on('connect', () => {
// The proxy connection is established, now we can use this socket
// to communicate with the target server
// If we need to do TLS for HTTPS targets, we would do it here
if (options.secureEndpoint) {
// For HTTPS, we would set up TLS on the socket
const tls = require('tls');
const tlsOptions = {
socket: proxySocket,
host: options.host,
rejectUnauthorized: options.rejectUnauthorized !== false,
...options.tlsOptions
};
const tlsSocket = tls.connect(tlsOptions, () => {
callback(null, tlsSocket);
});
tlsSocket.on('error', (err) => {
callback(err);
});
} else {
// For HTTP, we can use the socket directly
callback(null, proxySocket);
}
});
return proxySocket;
}
}
// Create a test server to proxy requests to
const targetServer = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
message: 'Hello from target server!',
timestamp: new Date().toISOString(),
clientIp: req.socket.remoteAddress,
method: req.method,
url: req.url
}));
});
// Create a simple proxy server for testing
const proxyServer = http.createServer((clientReq, clientRes) => {
// Parse the target URL from the request
const targetUrl = new URL(clientReq.url, `http://${clientReq.headers.host}`);
// Log the proxy request
console.log(`Proxying request to: ${targetUrl.href}`);
// Create a request to the target server
const proxyReq = http.request({
hostname: 'localhost',
port: targetServer.address().port,
path: targetUrl.pathname,
method: clientReq.method,
headers: {
...clientReq.headers,
'x-proxied': 'true',
'x-proxy-ip': clientReq.socket.remoteAddress
}
}, (proxyRes) => {
// Forward the response from the target server to the client
clientRes.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(clientRes);
});
// Handle errors
proxyReq.on('error', (err) => {
console.error('Proxy request error:', err);
if (!clientRes.headersSent) {
clientRes.writeHead(502, { 'Content-Type': 'text/plain' });
clientRes.end('Bad Gateway');
}
});
// Forward the request body if present
clientReq.pipe(proxyReq);
});
// Helper function to make a request through the proxy
async function makeRequestThroughProxy(url, proxyAgent) {
return new Promise((resolve, reject) => {
const req = http.get(url, { agent: proxyAgent }, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
resolve({
statusCode: res.statusCode,
headers: res.headers,
body: JSON.parse(data)
});
} catch (err) {
reject(new Error(`Failed to parse response: ${err.message}`));
}
});
});
req.on('error', reject);
req.end();
});
}
// Main function to demonstrate the proxy agent
async function runDemo() {
// Start the target server
await new Promise((resolve) => {
targetServer.listen(0, '127.0.0.1', resolve);
});
// Start the proxy server
await new Promise((resolve) => {
proxyServer.listen(0, '127.0.0.1', resolve);
});
const targetAddress = targetServer.address();
const proxyAddress = proxyServer.address();
console.log('Target server running at:', `http://${targetAddress.address}:${targetAddress.port}`);
console.log('Proxy server running at:', `http://${proxyAddress.address}:${proxyAddress.port}`);
// Create a proxy agent
const proxyAgent = new HttpProxyAgent(
`http://${proxyAddress.address}:${proxyAddress.port}`,
{
keepAlive: true,
maxSockets: 5
}
);
console.log('\n=== Making request through proxy ===');
try {
// Make a request through the proxy
const response = await makeRequestThroughProxy(
`http://localhost:${targetAddress.port}/api/test`,
proxyAgent
);
console.log('Response status code:', response.statusCode);
console.log('Response body:', JSON.stringify(response.body, null, 2));
// Make another request to demonstrate connection reuse
console.log('\n=== Making another request to demonstrate connection reuse ===');
const response2 = await makeRequestThroughProxy(
`http://localhost:${targetAddress.port}/api/another`,
proxyAgent
);
console.log('Second response status code:', response2.statusCode);
console.log('Response body:', JSON.stringify(response2.body, null, 2));
} catch (err) {
console.error('Error making request through proxy:', err);
} finally {
// Clean up
proxyAgent.destroy();
proxyServer.close();
targetServer.close();
console.log('\n=== Demo complete ===');
console.log('The proxy and target servers have been stopped.');
}
}
// Run the demo
runDemo().catch(console.error);