182 lines
5.5 KiB
TypeScript
182 lines
5.5 KiB
TypeScript
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(); |