Get your own Node server
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);

              
Target server running at: http://127.0.0.1:12345
Proxy server running at: http://127.0.0.1:54321

=== Making request through proxy ===
Proxying request to: http://localhost:12345/api/test
Response status code: 200
Response body: {
  "message": "Hello from target server!",
  "timestamp": "2025-06-18T07:30:45.123Z",
  "clientIp": "127.0.0.1",
  "method": "GET",
  "url": "/api/test"
}

=== Making another request to demonstrate connection reuse ===
Proxying request to: http://localhost:12345/api/another
Second response status code: 200
Response body: {
  "message": "Hello from target server!",
  "timestamp": "2025-06-18T07:30:45.345Z",
  "clientIp": "127.0.0.1",
  "method": "GET",
  "url": "/api/another"
}

=== Demo complete ===
The proxy and target servers have been stopped.