import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as plugins from '../ts/plugins.js'; import { SmartProxy } from '../ts/index.js'; let outerProxy: SmartProxy; let innerProxy: SmartProxy; tap.test('setup two smartproxies in a chain configuration', async () => { // Setup inner proxy (backend proxy) innerProxy = new SmartProxy({ routes: [ { match: { ports: 8002 }, action: { type: 'forward', target: { host: 'httpbin.org', port: 443 } } } ], defaults: { target: { host: 'httpbin.org', port: 443 } }, acceptProxyProtocol: true, sendProxyProtocol: false, enableDetailedLogging: true, connectionCleanupInterval: 5000, // More frequent cleanup for testing inactivityTimeout: 10000 // Shorter timeout for testing }); await innerProxy.start(); // Setup outer proxy (frontend proxy) outerProxy = new SmartProxy({ routes: [ { match: { ports: 8001 }, action: { type: 'forward', target: { host: 'localhost', port: 8002 }, sendProxyProtocol: true } } ], defaults: { target: { host: 'localhost', port: 8002 } }, sendProxyProtocol: true, enableDetailedLogging: true, connectionCleanupInterval: 5000, // More frequent cleanup for testing inactivityTimeout: 10000 // Shorter timeout for testing }); await outerProxy.start(); }); tap.test('should properly cleanup connections in proxy chain', async (tools) => { const testDuration = 30000; // 30 seconds const connectionInterval = 500; // Create new connection every 500ms const connectionDuration = 2000; // Each connection lasts 2 seconds let connectionsCreated = 0; let connectionsCompleted = 0; // Function to create a test connection const createTestConnection = async () => { connectionsCreated++; const connectionId = connectionsCreated; try { const socket = plugins.net.connect({ port: 8001, host: 'localhost' }); await new Promise((resolve, reject) => { socket.on('connect', () => { console.log(`Connection ${connectionId} established`); // Send TLS Client Hello for httpbin.org const clientHello = Buffer.from([ 0x16, 0x03, 0x01, 0x00, 0xc8, // TLS handshake header 0x01, 0x00, 0x00, 0xc4, // Client Hello 0x03, 0x03, // TLS 1.2 ...Array(32).fill(0), // Random bytes 0x00, // Session ID length 0x00, 0x02, 0x13, 0x01, // Cipher suites 0x01, 0x00, // Compression methods 0x00, 0x97, // Extensions length 0x00, 0x00, 0x00, 0x0f, 0x00, 0x0d, // SNI extension 0x00, 0x00, 0x0a, 0x68, 0x74, 0x74, 0x70, 0x62, 0x69, 0x6e, 0x2e, 0x6f, 0x72, 0x67 // "httpbin.org" ]); socket.write(clientHello); // Keep connection alive for specified duration setTimeout(() => { socket.destroy(); connectionsCompleted++; console.log(`Connection ${connectionId} closed (completed: ${connectionsCompleted}/${connectionsCreated})`); resolve(); }, connectionDuration); }); socket.on('error', (err) => { console.log(`Connection ${connectionId} error: ${err.message}`); connectionsCompleted++; reject(err); }); }); } catch (err) { console.log(`Failed to create connection ${connectionId}: ${err.message}`); connectionsCompleted++; } }; // Start creating connections const startTime = Date.now(); const connectionTimer = setInterval(() => { if (Date.now() - startTime < testDuration) { createTestConnection().catch(() => {}); } else { clearInterval(connectionTimer); } }, connectionInterval); // Monitor connection counts const monitorInterval = setInterval(() => { const outerConnections = (outerProxy as any).connectionManager.getConnectionCount(); const innerConnections = (innerProxy as any).connectionManager.getConnectionCount(); console.log(`Active connections - Outer: ${outerConnections}, Inner: ${innerConnections}, Created: ${connectionsCreated}, Completed: ${connectionsCompleted}`); }, 2000); // Wait for test duration + cleanup time await tools.delayFor(testDuration + 10000); clearInterval(connectionTimer); clearInterval(monitorInterval); // Wait for all connections to complete while (connectionsCompleted < connectionsCreated) { await tools.delayFor(100); } // Give some time for cleanup await tools.delayFor(5000); // Check final connection counts const finalOuterConnections = (outerProxy as any).connectionManager.getConnectionCount(); const finalInnerConnections = (innerProxy as any).connectionManager.getConnectionCount(); console.log(`\nFinal connection counts:`); console.log(`Outer proxy: ${finalOuterConnections}`); console.log(`Inner proxy: ${finalInnerConnections}`); console.log(`Total created: ${connectionsCreated}`); console.log(`Total completed: ${connectionsCompleted}`); // Both proxies should have cleaned up all connections expect(finalOuterConnections).toEqual(0); expect(finalInnerConnections).toEqual(0); }); tap.test('cleanup proxies', async () => { await outerProxy.stop(); await innerProxy.stop(); }); export default tap.start();