Implement connection timeout handling and improve connection cleanup in SmartProxy
This commit is contained in:
182
test/test.proxy-chain-cleanup.node.ts
Normal file
182
test/test.proxy-chain-cleanup.node.ts
Normal file
@ -0,0 +1,182 @@
|
||||
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<void>((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();
|
Reference in New Issue
Block a user