feat(socket-utils): implement socket cleanup utilities and enhance socket handling in forwarding handlers
This commit is contained in:
parent
c7c325a7d8
commit
eb2e67fecc
@ -591,13 +591,6 @@ tap.test('cleanup', async () => {
|
|||||||
|
|
||||||
// Exit handler removed to prevent interference with test cleanup
|
// Exit handler removed to prevent interference with test cleanup
|
||||||
|
|
||||||
// Add a post-hook to force exit after tap completion
|
// Teardown test removed - let tap handle proper cleanup
|
||||||
tap.test('teardown', async () => {
|
|
||||||
// Force exit after all tests complete
|
|
||||||
setTimeout(() => {
|
|
||||||
console.log('[TEST] Force exit after tap completion');
|
|
||||||
process.exit(0);
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default tap.start();
|
export default tap.start();
|
@ -16,3 +16,4 @@ export * from './fs-utils.js';
|
|||||||
export * from './lifecycle-component.js';
|
export * from './lifecycle-component.js';
|
||||||
export * from './binary-heap.js';
|
export * from './binary-heap.js';
|
||||||
export * from './enhanced-connection-pool.js';
|
export * from './enhanced-connection-pool.js';
|
||||||
|
export * from './socket-utils.js';
|
||||||
|
96
ts/core/utils/socket-utils.ts
Normal file
96
ts/core/utils/socket-utils.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import * as plugins from '../../plugins.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Safely cleanup a socket by removing all listeners and destroying it
|
||||||
|
* @param socket The socket to cleanup
|
||||||
|
* @param socketName Optional name for logging
|
||||||
|
*/
|
||||||
|
export function cleanupSocket(socket: plugins.net.Socket | plugins.tls.TLSSocket | null, socketName?: string): void {
|
||||||
|
if (!socket) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Remove all event listeners
|
||||||
|
socket.removeAllListeners();
|
||||||
|
|
||||||
|
// Unpipe any streams
|
||||||
|
socket.unpipe();
|
||||||
|
|
||||||
|
// Destroy if not already destroyed
|
||||||
|
if (!socket.destroyed) {
|
||||||
|
socket.destroy();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error cleaning up socket${socketName ? ` (${socketName})` : ''}: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a cleanup handler for paired sockets (client and server)
|
||||||
|
* @param clientSocket The client socket
|
||||||
|
* @param serverSocket The server socket (optional)
|
||||||
|
* @param onCleanup Optional callback when cleanup is done
|
||||||
|
* @returns A cleanup function that can be called multiple times safely
|
||||||
|
*/
|
||||||
|
export function createSocketCleanupHandler(
|
||||||
|
clientSocket: plugins.net.Socket | plugins.tls.TLSSocket,
|
||||||
|
serverSocket?: plugins.net.Socket | plugins.tls.TLSSocket | null,
|
||||||
|
onCleanup?: (reason: string) => void
|
||||||
|
): (reason: string) => void {
|
||||||
|
let cleanedUp = false;
|
||||||
|
|
||||||
|
return (reason: string) => {
|
||||||
|
if (cleanedUp) return;
|
||||||
|
cleanedUp = true;
|
||||||
|
|
||||||
|
// Cleanup both sockets
|
||||||
|
cleanupSocket(clientSocket, 'client');
|
||||||
|
if (serverSocket) {
|
||||||
|
cleanupSocket(serverSocket, 'server');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call cleanup callback if provided
|
||||||
|
if (onCleanup) {
|
||||||
|
onCleanup(reason);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup socket error and close handlers with proper cleanup
|
||||||
|
* @param socket The socket to setup handlers for
|
||||||
|
* @param handleClose The cleanup function to call
|
||||||
|
* @param errorPrefix Optional prefix for error messages
|
||||||
|
*/
|
||||||
|
export function setupSocketHandlers(
|
||||||
|
socket: plugins.net.Socket | plugins.tls.TLSSocket,
|
||||||
|
handleClose: (reason: string) => void,
|
||||||
|
errorPrefix?: string
|
||||||
|
): void {
|
||||||
|
socket.on('error', (error) => {
|
||||||
|
const prefix = errorPrefix || 'Socket';
|
||||||
|
handleClose(`${prefix}_error: ${error.message}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('close', () => {
|
||||||
|
const prefix = errorPrefix || 'socket';
|
||||||
|
handleClose(`${prefix}_closed`);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('timeout', () => {
|
||||||
|
const prefix = errorPrefix || 'socket';
|
||||||
|
handleClose(`${prefix}_timeout`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pipe two sockets together with proper cleanup on either end
|
||||||
|
* @param socket1 First socket
|
||||||
|
* @param socket2 Second socket
|
||||||
|
*/
|
||||||
|
export function pipeSockets(
|
||||||
|
socket1: plugins.net.Socket | plugins.tls.TLSSocket,
|
||||||
|
socket2: plugins.net.Socket | plugins.tls.TLSSocket
|
||||||
|
): void {
|
||||||
|
socket1.pipe(socket2);
|
||||||
|
socket2.pipe(socket1);
|
||||||
|
}
|
@ -2,6 +2,7 @@ import * as plugins from '../../plugins.js';
|
|||||||
import { ForwardingHandler } from './base-handler.js';
|
import { ForwardingHandler } from './base-handler.js';
|
||||||
import type { IForwardConfig } from '../config/forwarding-types.js';
|
import type { IForwardConfig } from '../config/forwarding-types.js';
|
||||||
import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
|
import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
|
||||||
|
import { setupSocketHandlers } from '../../core/utils/socket-utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for HTTP-only forwarding
|
* Handler for HTTP-only forwarding
|
||||||
@ -40,12 +41,15 @@ export class HttpForwardingHandler extends ForwardingHandler {
|
|||||||
const remoteAddress = socket.remoteAddress || 'unknown';
|
const remoteAddress = socket.remoteAddress || 'unknown';
|
||||||
const localPort = socket.localPort || 80;
|
const localPort = socket.localPort || 80;
|
||||||
|
|
||||||
socket.on('close', (hadError) => {
|
// Set up socket handlers with proper cleanup
|
||||||
|
const handleClose = (reason: string) => {
|
||||||
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
||||||
remoteAddress,
|
remoteAddress,
|
||||||
hadError
|
reason
|
||||||
});
|
});
|
||||||
});
|
};
|
||||||
|
|
||||||
|
setupSocketHandlers(socket, handleClose, 'http');
|
||||||
|
|
||||||
socket.on('error', (error) => {
|
socket.on('error', (error) => {
|
||||||
this.emit(ForwardingHandlerEvents.ERROR, {
|
this.emit(ForwardingHandlerEvents.ERROR, {
|
||||||
|
@ -2,6 +2,7 @@ import * as plugins from '../../plugins.js';
|
|||||||
import { ForwardingHandler } from './base-handler.js';
|
import { ForwardingHandler } from './base-handler.js';
|
||||||
import type { IForwardConfig } from '../config/forwarding-types.js';
|
import type { IForwardConfig } from '../config/forwarding-types.js';
|
||||||
import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
|
import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
|
||||||
|
import { createSocketCleanupHandler, setupSocketHandlers, pipeSockets } from '../../core/utils/socket-utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for HTTPS passthrough (SNI forwarding without termination)
|
* Handler for HTTPS passthrough (SNI forwarding without termination)
|
||||||
@ -50,36 +51,24 @@ export class HttpsPassthroughHandler extends ForwardingHandler {
|
|||||||
// Create a connection to the target server
|
// Create a connection to the target server
|
||||||
const serverSocket = plugins.net.connect(target.port, target.host);
|
const serverSocket = plugins.net.connect(target.port, target.host);
|
||||||
|
|
||||||
// Handle errors on the server socket
|
|
||||||
serverSocket.on('error', (error) => {
|
|
||||||
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
||||||
remoteAddress,
|
|
||||||
error: `Target connection error: ${error.message}`
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close the client socket if it's still open
|
|
||||||
if (!clientSocket.destroyed) {
|
|
||||||
clientSocket.destroy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle errors on the client socket
|
|
||||||
clientSocket.on('error', (error) => {
|
|
||||||
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
||||||
remoteAddress,
|
|
||||||
error: `Client connection error: ${error.message}`
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close the server socket if it's still open
|
|
||||||
if (!serverSocket.destroyed) {
|
|
||||||
serverSocket.destroy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Track data transfer for logging
|
// Track data transfer for logging
|
||||||
let bytesSent = 0;
|
let bytesSent = 0;
|
||||||
let bytesReceived = 0;
|
let bytesReceived = 0;
|
||||||
|
|
||||||
|
// Create cleanup handler with our utility
|
||||||
|
const handleClose = createSocketCleanupHandler(clientSocket, serverSocket, (reason) => {
|
||||||
|
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
||||||
|
remoteAddress,
|
||||||
|
bytesSent,
|
||||||
|
bytesReceived,
|
||||||
|
reason
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup error and close handlers for both sockets
|
||||||
|
setupSocketHandlers(serverSocket, handleClose, 'server');
|
||||||
|
setupSocketHandlers(clientSocket, handleClose, 'client');
|
||||||
|
|
||||||
// Forward data from client to server
|
// Forward data from client to server
|
||||||
clientSocket.on('data', (data) => {
|
clientSocket.on('data', (data) => {
|
||||||
bytesSent += data.length;
|
bytesSent += data.length;
|
||||||
@ -128,48 +117,10 @@ export class HttpsPassthroughHandler extends ForwardingHandler {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle connection close
|
|
||||||
const handleClose = () => {
|
|
||||||
if (!clientSocket.destroyed) {
|
|
||||||
clientSocket.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!serverSocket.destroyed) {
|
|
||||||
serverSocket.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
|
||||||
remoteAddress,
|
|
||||||
bytesSent,
|
|
||||||
bytesReceived
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set up close handlers
|
|
||||||
clientSocket.on('close', handleClose);
|
|
||||||
serverSocket.on('close', handleClose);
|
|
||||||
|
|
||||||
// Set timeouts
|
// Set timeouts
|
||||||
const timeout = this.getTimeout();
|
const timeout = this.getTimeout();
|
||||||
clientSocket.setTimeout(timeout);
|
clientSocket.setTimeout(timeout);
|
||||||
serverSocket.setTimeout(timeout);
|
serverSocket.setTimeout(timeout);
|
||||||
|
|
||||||
// Handle timeouts
|
|
||||||
clientSocket.on('timeout', () => {
|
|
||||||
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
||||||
remoteAddress,
|
|
||||||
error: 'Client connection timeout'
|
|
||||||
});
|
|
||||||
handleClose();
|
|
||||||
});
|
|
||||||
|
|
||||||
serverSocket.on('timeout', () => {
|
|
||||||
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
||||||
remoteAddress,
|
|
||||||
error: 'Server connection timeout'
|
|
||||||
});
|
|
||||||
handleClose();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,6 +2,7 @@ import * as plugins from '../../plugins.js';
|
|||||||
import { ForwardingHandler } from './base-handler.js';
|
import { ForwardingHandler } from './base-handler.js';
|
||||||
import type { IForwardConfig } from '../config/forwarding-types.js';
|
import type { IForwardConfig } from '../config/forwarding-types.js';
|
||||||
import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
|
import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
|
||||||
|
import { createSocketCleanupHandler, setupSocketHandlers } from '../../core/utils/socket-utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for HTTPS termination with HTTP backend
|
* Handler for HTTPS termination with HTTP backend
|
||||||
@ -95,62 +96,24 @@ export class HttpsTerminateToHttpHandler extends ForwardingHandler {
|
|||||||
tls: true
|
tls: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle TLS errors
|
// Variables to track connections
|
||||||
tlsSocket.on('error', (error) => {
|
let backendSocket: plugins.net.Socket | null = null;
|
||||||
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
||||||
remoteAddress,
|
|
||||||
error: `TLS error: ${error.message}`
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!tlsSocket.destroyed) {
|
|
||||||
tlsSocket.destroy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// The TLS socket will now emit HTTP traffic that can be processed
|
|
||||||
// In a real implementation, we would create an HTTP parser and handle
|
|
||||||
// the requests here, but for simplicity, we'll just log the data
|
|
||||||
|
|
||||||
let dataBuffer = Buffer.alloc(0);
|
let dataBuffer = Buffer.alloc(0);
|
||||||
|
let connectionEstablished = false;
|
||||||
|
|
||||||
tlsSocket.on('data', (data) => {
|
// Create cleanup handler for all sockets
|
||||||
// Append to buffer
|
const handleClose = createSocketCleanupHandler(tlsSocket, backendSocket, (reason) => {
|
||||||
dataBuffer = Buffer.concat([dataBuffer, data]);
|
|
||||||
|
|
||||||
// Very basic HTTP parsing - in a real implementation, use http-parser
|
|
||||||
if (dataBuffer.includes(Buffer.from('\r\n\r\n'))) {
|
|
||||||
const target = this.getTargetFromConfig();
|
|
||||||
|
|
||||||
// Simple example: forward the data to an HTTP server
|
|
||||||
const socket = plugins.net.connect(target.port, target.host, () => {
|
|
||||||
socket.write(dataBuffer);
|
|
||||||
dataBuffer = Buffer.alloc(0);
|
|
||||||
|
|
||||||
// Set up bidirectional data flow
|
|
||||||
tlsSocket.pipe(socket);
|
|
||||||
socket.pipe(tlsSocket);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('error', (error) => {
|
|
||||||
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
||||||
remoteAddress,
|
|
||||||
error: `Target connection error: ${error.message}`
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!tlsSocket.destroyed) {
|
|
||||||
tlsSocket.destroy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle close
|
|
||||||
tlsSocket.on('close', () => {
|
|
||||||
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
||||||
remoteAddress
|
remoteAddress,
|
||||||
|
reason
|
||||||
});
|
});
|
||||||
|
dataBuffer = Buffer.alloc(0);
|
||||||
|
connectionEstablished = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Set up error handling with our cleanup utility
|
||||||
|
setupSocketHandlers(tlsSocket, handleClose, 'tls');
|
||||||
|
|
||||||
// Set timeout
|
// Set timeout
|
||||||
const timeout = this.getTimeout();
|
const timeout = this.getTimeout();
|
||||||
tlsSocket.setTimeout(timeout);
|
tlsSocket.setTimeout(timeout);
|
||||||
@ -160,9 +123,58 @@ export class HttpsTerminateToHttpHandler extends ForwardingHandler {
|
|||||||
remoteAddress,
|
remoteAddress,
|
||||||
error: 'TLS connection timeout'
|
error: 'TLS connection timeout'
|
||||||
});
|
});
|
||||||
|
handleClose('timeout');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle TLS data
|
||||||
|
tlsSocket.on('data', (data) => {
|
||||||
|
// If backend connection already established, just forward the data
|
||||||
|
if (connectionEstablished && backendSocket && !backendSocket.destroyed) {
|
||||||
|
backendSocket.write(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!tlsSocket.destroyed) {
|
// Append to buffer
|
||||||
tlsSocket.destroy();
|
dataBuffer = Buffer.concat([dataBuffer, data]);
|
||||||
|
|
||||||
|
// Very basic HTTP parsing - in a real implementation, use http-parser
|
||||||
|
if (dataBuffer.includes(Buffer.from('\r\n\r\n')) && !connectionEstablished) {
|
||||||
|
const target = this.getTargetFromConfig();
|
||||||
|
|
||||||
|
// Create backend connection
|
||||||
|
backendSocket = plugins.net.connect(target.port, target.host, () => {
|
||||||
|
connectionEstablished = true;
|
||||||
|
|
||||||
|
// Send buffered data
|
||||||
|
if (dataBuffer.length > 0) {
|
||||||
|
backendSocket!.write(dataBuffer);
|
||||||
|
dataBuffer = Buffer.alloc(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up bidirectional data flow
|
||||||
|
tlsSocket.pipe(backendSocket!);
|
||||||
|
backendSocket!.pipe(tlsSocket);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the cleanup handler with the backend socket
|
||||||
|
const newHandleClose = createSocketCleanupHandler(tlsSocket, backendSocket, (reason) => {
|
||||||
|
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
||||||
|
remoteAddress,
|
||||||
|
reason
|
||||||
|
});
|
||||||
|
dataBuffer = Buffer.alloc(0);
|
||||||
|
connectionEstablished = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up handlers for backend socket
|
||||||
|
setupSocketHandlers(backendSocket, newHandleClose, 'backend');
|
||||||
|
|
||||||
|
backendSocket.on('error', (error) => {
|
||||||
|
this.emit(ForwardingHandlerEvents.ERROR, {
|
||||||
|
remoteAddress,
|
||||||
|
error: `Target connection error: ${error.message}`
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import * as plugins from '../../plugins.js';
|
|||||||
import { ForwardingHandler } from './base-handler.js';
|
import { ForwardingHandler } from './base-handler.js';
|
||||||
import type { IForwardConfig } from '../config/forwarding-types.js';
|
import type { IForwardConfig } from '../config/forwarding-types.js';
|
||||||
import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
|
import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
|
||||||
|
import { createSocketCleanupHandler, setupSocketHandlers } from '../../core/utils/socket-utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for HTTPS termination with HTTPS backend
|
* Handler for HTTPS termination with HTTPS backend
|
||||||
@ -93,28 +94,38 @@ export class HttpsTerminateToHttpsHandler extends ForwardingHandler {
|
|||||||
tls: true
|
tls: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle TLS errors
|
// Variable to track backend socket
|
||||||
tlsSocket.on('error', (error) => {
|
let backendSocket: plugins.tls.TLSSocket | null = null;
|
||||||
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
||||||
|
// Create cleanup handler for both sockets
|
||||||
|
const handleClose = createSocketCleanupHandler(tlsSocket, backendSocket, (reason) => {
|
||||||
|
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
||||||
remoteAddress,
|
remoteAddress,
|
||||||
error: `TLS error: ${error.message}`
|
reason
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!tlsSocket.destroyed) {
|
|
||||||
tlsSocket.destroy();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// The TLS socket will now emit HTTP traffic that can be processed
|
// Set up error handling with our cleanup utility
|
||||||
// In a real implementation, we would create an HTTP parser and handle
|
setupSocketHandlers(tlsSocket, handleClose, 'tls');
|
||||||
// the requests here, but for simplicity, we'll just forward the data
|
|
||||||
|
// Set timeout
|
||||||
|
const timeout = this.getTimeout();
|
||||||
|
tlsSocket.setTimeout(timeout);
|
||||||
|
|
||||||
|
tlsSocket.on('timeout', () => {
|
||||||
|
this.emit(ForwardingHandlerEvents.ERROR, {
|
||||||
|
remoteAddress,
|
||||||
|
error: 'TLS connection timeout'
|
||||||
|
});
|
||||||
|
handleClose('timeout');
|
||||||
|
});
|
||||||
|
|
||||||
// Get the target from configuration
|
// Get the target from configuration
|
||||||
const target = this.getTargetFromConfig();
|
const target = this.getTargetFromConfig();
|
||||||
|
|
||||||
// Set up the connection to the HTTPS backend
|
// Set up the connection to the HTTPS backend
|
||||||
const connectToBackend = () => {
|
const connectToBackend = () => {
|
||||||
const backendSocket = plugins.tls.connect({
|
backendSocket = plugins.tls.connect({
|
||||||
host: target.host,
|
host: target.host,
|
||||||
port: target.port,
|
port: target.port,
|
||||||
// In a real implementation, we would configure TLS options
|
// In a real implementation, we would configure TLS options
|
||||||
@ -127,30 +138,29 @@ export class HttpsTerminateToHttpsHandler extends ForwardingHandler {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Set up bidirectional data flow
|
// Set up bidirectional data flow
|
||||||
tlsSocket.pipe(backendSocket);
|
tlsSocket.pipe(backendSocket!);
|
||||||
backendSocket.pipe(tlsSocket);
|
backendSocket!.pipe(tlsSocket);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update the cleanup handler with the backend socket
|
||||||
|
const newHandleClose = createSocketCleanupHandler(tlsSocket, backendSocket, (reason) => {
|
||||||
|
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
||||||
|
remoteAddress,
|
||||||
|
reason
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up handlers for backend socket
|
||||||
|
setupSocketHandlers(backendSocket, newHandleClose, 'backend');
|
||||||
|
|
||||||
backendSocket.on('error', (error) => {
|
backendSocket.on('error', (error) => {
|
||||||
this.emit(ForwardingHandlerEvents.ERROR, {
|
this.emit(ForwardingHandlerEvents.ERROR, {
|
||||||
remoteAddress,
|
remoteAddress,
|
||||||
error: `Backend connection error: ${error.message}`
|
error: `Backend connection error: ${error.message}`
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!tlsSocket.destroyed) {
|
|
||||||
tlsSocket.destroy();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle close
|
// Set timeout for backend socket
|
||||||
backendSocket.on('close', () => {
|
|
||||||
if (!tlsSocket.destroyed) {
|
|
||||||
tlsSocket.destroy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set timeout
|
|
||||||
const timeout = this.getTimeout();
|
|
||||||
backendSocket.setTimeout(timeout);
|
backendSocket.setTimeout(timeout);
|
||||||
|
|
||||||
backendSocket.on('timeout', () => {
|
backendSocket.on('timeout', () => {
|
||||||
@ -158,10 +168,7 @@ export class HttpsTerminateToHttpsHandler extends ForwardingHandler {
|
|||||||
remoteAddress,
|
remoteAddress,
|
||||||
error: 'Backend connection timeout'
|
error: 'Backend connection timeout'
|
||||||
});
|
});
|
||||||
|
newHandleClose('backend_timeout');
|
||||||
if (!backendSocket.destroyed) {
|
|
||||||
backendSocket.destroy();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -169,28 +176,6 @@ export class HttpsTerminateToHttpsHandler extends ForwardingHandler {
|
|||||||
tlsSocket.on('secure', () => {
|
tlsSocket.on('secure', () => {
|
||||||
connectToBackend();
|
connectToBackend();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle close
|
|
||||||
tlsSocket.on('close', () => {
|
|
||||||
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
|
||||||
remoteAddress
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set timeout
|
|
||||||
const timeout = this.getTimeout();
|
|
||||||
tlsSocket.setTimeout(timeout);
|
|
||||||
|
|
||||||
tlsSocket.on('timeout', () => {
|
|
||||||
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
||||||
remoteAddress,
|
|
||||||
error: 'TLS connection timeout'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!tlsSocket.destroyed) {
|
|
||||||
tlsSocket.destroy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -128,10 +128,24 @@ export class HttpProxyBridge {
|
|||||||
proxySocket.pipe(socket);
|
proxySocket.pipe(socket);
|
||||||
|
|
||||||
// Handle cleanup
|
// Handle cleanup
|
||||||
|
let cleanedUp = false;
|
||||||
const cleanup = (reason: string) => {
|
const cleanup = (reason: string) => {
|
||||||
|
if (cleanedUp) return;
|
||||||
|
cleanedUp = true;
|
||||||
|
|
||||||
|
// Remove all event listeners to prevent memory leaks
|
||||||
|
socket.removeAllListeners('end');
|
||||||
|
socket.removeAllListeners('error');
|
||||||
|
proxySocket.removeAllListeners('end');
|
||||||
|
proxySocket.removeAllListeners('error');
|
||||||
|
|
||||||
socket.unpipe(proxySocket);
|
socket.unpipe(proxySocket);
|
||||||
proxySocket.unpipe(socket);
|
proxySocket.unpipe(socket);
|
||||||
proxySocket.destroy();
|
|
||||||
|
if (!proxySocket.destroyed) {
|
||||||
|
proxySocket.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
cleanupCallback(reason);
|
cleanupCallback(reason);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -822,6 +822,38 @@ export class RouteConnectionHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Track event listeners added by the handler so we can clean them up
|
||||||
|
const originalOn = socket.on.bind(socket);
|
||||||
|
const originalOnce = socket.once.bind(socket);
|
||||||
|
const trackedListeners: Array<{event: string; listener: Function}> = [];
|
||||||
|
|
||||||
|
// Override socket.on to track listeners
|
||||||
|
socket.on = function(event: string, listener: Function) {
|
||||||
|
trackedListeners.push({event, listener});
|
||||||
|
return originalOn(event, listener);
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
// Override socket.once to track listeners
|
||||||
|
socket.once = function(event: string, listener: Function) {
|
||||||
|
trackedListeners.push({event, listener});
|
||||||
|
return originalOnce(event, listener);
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
// Set up automatic cleanup when socket closes
|
||||||
|
const cleanupHandler = () => {
|
||||||
|
// Remove all tracked listeners
|
||||||
|
for (const {event, listener} of trackedListeners) {
|
||||||
|
socket.removeListener(event, listener);
|
||||||
|
}
|
||||||
|
// Restore original methods
|
||||||
|
socket.on = originalOn;
|
||||||
|
socket.once = originalOnce;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Listen for socket close to trigger cleanup
|
||||||
|
originalOnce('close', cleanupHandler);
|
||||||
|
originalOnce('error', cleanupHandler);
|
||||||
|
|
||||||
// Create route context for the handler
|
// Create route context for the handler
|
||||||
const routeContext = this.createRouteContext({
|
const routeContext = this.createRouteContext({
|
||||||
connectionId: record.id,
|
connectionId: record.id,
|
||||||
@ -855,6 +887,8 @@ export class RouteConnectionHandler {
|
|||||||
error: error.message,
|
error: error.message,
|
||||||
component: 'route-handler'
|
component: 'route-handler'
|
||||||
});
|
});
|
||||||
|
// Remove all event listeners before destroying to prevent memory leaks
|
||||||
|
socket.removeAllListeners();
|
||||||
if (!socket.destroyed) {
|
if (!socket.destroyed) {
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
}
|
}
|
||||||
@ -875,6 +909,8 @@ export class RouteConnectionHandler {
|
|||||||
error: error.message,
|
error: error.message,
|
||||||
component: 'route-handler'
|
component: 'route-handler'
|
||||||
});
|
});
|
||||||
|
// Remove all event listeners before destroying to prevent memory leaks
|
||||||
|
socket.removeAllListeners();
|
||||||
if (!socket.destroyed) {
|
if (!socket.destroyed) {
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user