195 lines
5.4 KiB
TypeScript
195 lines
5.4 KiB
TypeScript
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||
|
import * as net from 'net';
|
||
|
import * as plugins from '../ts/plugins.js';
|
||
|
|
||
|
// Import SmartProxy and configurations
|
||
|
import { SmartProxy } from '../ts/index.js';
|
||
|
|
||
|
tap.test('simple proxy chain test - identify connection accumulation', async () => {
|
||
|
console.log('\n=== Simple Proxy Chain Test ===');
|
||
|
console.log('Setup: Client → SmartProxy1 (8590) → SmartProxy2 (8591) → Backend (down)');
|
||
|
|
||
|
// Create backend server that accepts and immediately closes connections
|
||
|
const backend = net.createServer((socket) => {
|
||
|
console.log('Backend: Connection received, closing immediately');
|
||
|
socket.destroy();
|
||
|
});
|
||
|
|
||
|
await new Promise<void>((resolve) => {
|
||
|
backend.listen(9998, () => {
|
||
|
console.log('✓ Backend server started on port 9998 (closes connections immediately)');
|
||
|
resolve();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
// Create SmartProxy2 (downstream)
|
||
|
const proxy2 = new SmartProxy({
|
||
|
ports: [8591],
|
||
|
enableDetailedLogging: true,
|
||
|
socketTimeout: 5000,
|
||
|
routes: [{
|
||
|
name: 'to-backend',
|
||
|
match: { ports: 8591 },
|
||
|
action: {
|
||
|
type: 'forward',
|
||
|
target: {
|
||
|
host: 'localhost',
|
||
|
port: 9998 // Backend that closes immediately
|
||
|
}
|
||
|
}
|
||
|
}]
|
||
|
});
|
||
|
|
||
|
// Create SmartProxy1 (upstream)
|
||
|
const proxy1 = new SmartProxy({
|
||
|
ports: [8590],
|
||
|
enableDetailedLogging: true,
|
||
|
socketTimeout: 5000,
|
||
|
routes: [{
|
||
|
name: 'to-proxy2',
|
||
|
match: { ports: 8590 },
|
||
|
action: {
|
||
|
type: 'forward',
|
||
|
target: {
|
||
|
host: 'localhost',
|
||
|
port: 8591 // Forward to proxy2
|
||
|
}
|
||
|
}
|
||
|
}]
|
||
|
});
|
||
|
|
||
|
await proxy2.start();
|
||
|
console.log('✓ SmartProxy2 started on port 8591');
|
||
|
|
||
|
await proxy1.start();
|
||
|
console.log('✓ SmartProxy1 started on port 8590');
|
||
|
|
||
|
// Helper to get connection counts
|
||
|
const getConnectionCounts = () => {
|
||
|
const conn1 = (proxy1 as any).connectionManager;
|
||
|
const conn2 = (proxy2 as any).connectionManager;
|
||
|
return {
|
||
|
proxy1: conn1 ? conn1.getConnectionCount() : 0,
|
||
|
proxy2: conn2 ? conn2.getConnectionCount() : 0
|
||
|
};
|
||
|
};
|
||
|
|
||
|
console.log('\n--- Making 5 sequential connections ---');
|
||
|
|
||
|
for (let i = 0; i < 5; i++) {
|
||
|
console.log(`\n=== Connection ${i + 1} ===`);
|
||
|
|
||
|
const counts = getConnectionCounts();
|
||
|
console.log(`Before: Proxy1=${counts.proxy1}, Proxy2=${counts.proxy2}`);
|
||
|
|
||
|
await new Promise<void>((resolve) => {
|
||
|
const client = new net.Socket();
|
||
|
let dataReceived = false;
|
||
|
|
||
|
client.on('data', (data) => {
|
||
|
console.log(`Client received data: ${data.toString()}`);
|
||
|
dataReceived = true;
|
||
|
});
|
||
|
|
||
|
client.on('error', (err) => {
|
||
|
console.log(`Client error: ${err.code}`);
|
||
|
resolve();
|
||
|
});
|
||
|
|
||
|
client.on('close', () => {
|
||
|
console.log(`Client closed (data received: ${dataReceived})`);
|
||
|
resolve();
|
||
|
});
|
||
|
|
||
|
client.connect(8590, 'localhost', () => {
|
||
|
console.log('Client connected to Proxy1');
|
||
|
// Send HTTP request
|
||
|
client.write('GET / HTTP/1.1\r\nHost: test.com\r\n\r\n');
|
||
|
});
|
||
|
|
||
|
// Timeout
|
||
|
setTimeout(() => {
|
||
|
if (!client.destroyed) {
|
||
|
console.log('Client timeout, destroying');
|
||
|
client.destroy();
|
||
|
}
|
||
|
resolve();
|
||
|
}, 2000);
|
||
|
});
|
||
|
|
||
|
// Wait a bit and check counts
|
||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||
|
|
||
|
const afterCounts = getConnectionCounts();
|
||
|
console.log(`After: Proxy1=${afterCounts.proxy1}, Proxy2=${afterCounts.proxy2}`);
|
||
|
|
||
|
if (afterCounts.proxy1 > 0 || afterCounts.proxy2 > 0) {
|
||
|
console.log('⚠️ WARNING: Connections not cleaned up!');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
console.log('\n--- Test with backend completely down ---');
|
||
|
|
||
|
// Stop backend
|
||
|
backend.close();
|
||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||
|
console.log('✓ Backend stopped');
|
||
|
|
||
|
// Make more connections with backend down
|
||
|
for (let i = 0; i < 3; i++) {
|
||
|
console.log(`\n=== Connection ${i + 6} (backend down) ===`);
|
||
|
|
||
|
const counts = getConnectionCounts();
|
||
|
console.log(`Before: Proxy1=${counts.proxy1}, Proxy2=${counts.proxy2}`);
|
||
|
|
||
|
await new Promise<void>((resolve) => {
|
||
|
const client = new net.Socket();
|
||
|
|
||
|
client.on('error', () => {
|
||
|
resolve();
|
||
|
});
|
||
|
|
||
|
client.on('close', () => {
|
||
|
resolve();
|
||
|
});
|
||
|
|
||
|
client.connect(8590, 'localhost', () => {
|
||
|
client.write('GET / HTTP/1.1\r\nHost: test.com\r\n\r\n');
|
||
|
});
|
||
|
|
||
|
setTimeout(() => {
|
||
|
if (!client.destroyed) {
|
||
|
client.destroy();
|
||
|
}
|
||
|
resolve();
|
||
|
}, 1000);
|
||
|
});
|
||
|
|
||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||
|
|
||
|
const afterCounts = getConnectionCounts();
|
||
|
console.log(`After: Proxy1=${afterCounts.proxy1}, Proxy2=${afterCounts.proxy2}`);
|
||
|
}
|
||
|
|
||
|
// Final check
|
||
|
console.log('\n--- Final Check ---');
|
||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||
|
|
||
|
const finalCounts = getConnectionCounts();
|
||
|
console.log(`Final counts: Proxy1=${finalCounts.proxy1}, Proxy2=${finalCounts.proxy2}`);
|
||
|
|
||
|
await proxy1.stop();
|
||
|
await proxy2.stop();
|
||
|
|
||
|
// Verify
|
||
|
if (finalCounts.proxy1 > 0 || finalCounts.proxy2 > 0) {
|
||
|
console.log('\n❌ FAIL: Connections accumulated!');
|
||
|
} else {
|
||
|
console.log('\n✅ PASS: No connection accumulation');
|
||
|
}
|
||
|
|
||
|
expect(finalCounts.proxy1).toEqual(0);
|
||
|
expect(finalCounts.proxy2).toEqual(0);
|
||
|
});
|
||
|
|
||
|
tap.start();
|