UNPKG

@push.rocks/smartproxy

Version:

A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.

210 lines 17.7 kB
import * as plugins from '../plugins.js'; import { createLogger } from './classes.np.types.js'; /** * Manages a pool of backend connections for efficient reuse */ export class ConnectionPool { constructor(options) { this.options = options; this.connectionPool = new Map(); this.roundRobinPositions = new Map(); this.logger = createLogger(options.logLevel || 'info'); } /** * Get a connection from the pool or create a new one */ getConnection(host, port) { return new Promise((resolve, reject) => { const poolKey = `${host}:${port}`; const connectionList = this.connectionPool.get(poolKey) || []; // Look for an idle connection const idleConnectionIndex = connectionList.findIndex(c => c.isIdle); if (idleConnectionIndex >= 0) { // Get existing connection from pool const connection = connectionList[idleConnectionIndex]; connection.isIdle = false; connection.lastUsed = Date.now(); this.logger.debug(`Reusing connection from pool for ${poolKey}`); // Update the pool this.connectionPool.set(poolKey, connectionList); resolve(connection.socket); return; } // No idle connection available, create a new one if pool isn't full const poolSize = this.options.connectionPoolSize || 50; if (connectionList.length < poolSize) { this.logger.debug(`Creating new connection to ${host}:${port}`); try { const socket = plugins.net.connect({ host, port, keepAlive: true, keepAliveInitialDelay: 30000 // 30 seconds }); socket.once('connect', () => { // Add to connection pool const connection = { socket, lastUsed: Date.now(), isIdle: false }; connectionList.push(connection); this.connectionPool.set(poolKey, connectionList); // Setup cleanup when the connection is closed socket.once('close', () => { const idx = connectionList.findIndex(c => c.socket === socket); if (idx >= 0) { connectionList.splice(idx, 1); this.connectionPool.set(poolKey, connectionList); this.logger.debug(`Removed closed connection from pool for ${poolKey}`); } }); resolve(socket); }); socket.once('error', (err) => { this.logger.error(`Error creating connection to ${host}:${port}`, err); reject(err); }); } catch (err) { this.logger.error(`Failed to create connection to ${host}:${port}`, err); reject(err); } } else { // Pool is full, wait for an idle connection or reject this.logger.warn(`Connection pool for ${poolKey} is full (${connectionList.length})`); reject(new Error(`Connection pool for ${poolKey} is full`)); } }); } /** * Return a connection to the pool for reuse */ returnConnection(socket, host, port) { const poolKey = `${host}:${port}`; const connectionList = this.connectionPool.get(poolKey) || []; // Find this connection in the pool const connectionIndex = connectionList.findIndex(c => c.socket === socket); if (connectionIndex >= 0) { // Mark as idle and update last used time connectionList[connectionIndex].isIdle = true; connectionList[connectionIndex].lastUsed = Date.now(); this.logger.debug(`Returned connection to pool for ${poolKey}`); } else { this.logger.warn(`Attempted to return unknown connection to pool for ${poolKey}`); } } /** * Cleanup the connection pool by removing idle connections * or reducing pool size if it exceeds the configured maximum */ cleanupConnectionPool() { const now = Date.now(); const idleTimeout = this.options.keepAliveTimeout || 120000; // 2 minutes default for (const [host, connections] of this.connectionPool.entries()) { // Sort by last used time (oldest first) connections.sort((a, b) => a.lastUsed - b.lastUsed); // Remove idle connections older than the idle timeout let removed = 0; while (connections.length > 0) { const connection = connections[0]; // Remove if idle and exceeds timeout, or if pool is too large if ((connection.isIdle && now - connection.lastUsed > idleTimeout) || connections.length > (this.options.connectionPoolSize || 50)) { try { if (!connection.socket.destroyed) { connection.socket.end(); connection.socket.destroy(); } } catch (err) { this.logger.error(`Error destroying pooled connection to ${host}`, err); } connections.shift(); // Remove from pool removed++; } else { break; // Stop removing if we've reached active or recent connections } } if (removed > 0) { this.logger.debug(`Removed ${removed} idle connections from pool for ${host}, ${connections.length} remaining`); } // Update the pool with the remaining connections if (connections.length === 0) { this.connectionPool.delete(host); } else { this.connectionPool.set(host, connections); } } } /** * Close all connections in the pool */ closeAllConnections() { for (const [host, connections] of this.connectionPool.entries()) { this.logger.debug(`Closing ${connections.length} connections to ${host}`); for (const connection of connections) { try { if (!connection.socket.destroyed) { connection.socket.end(); connection.socket.destroy(); } } catch (error) { this.logger.error(`Error closing connection to ${host}:`, error); } } } this.connectionPool.clear(); this.roundRobinPositions.clear(); } /** * Get load balancing target using round-robin */ getNextTarget(targets, port) { const targetKey = targets.join(','); // Initialize position if not exists if (!this.roundRobinPositions.has(targetKey)) { this.roundRobinPositions.set(targetKey, 0); } // Get current position and increment for next time const currentPosition = this.roundRobinPositions.get(targetKey); const nextPosition = (currentPosition + 1) % targets.length; this.roundRobinPositions.set(targetKey, nextPosition); // Return the selected target return { host: targets[currentPosition], port }; } /** * Gets the connection pool status */ getPoolStatus() { return Object.fromEntries(Array.from(this.connectionPool.entries()).map(([host, connections]) => [ host, { total: connections.length, idle: connections.filter(c => c.isIdle).length } ])); } /** * Setup a periodic cleanup task */ setupPeriodicCleanup(interval = 60000) { const timer = setInterval(() => { this.cleanupConnectionPool(); }, interval); // Don't prevent process exit if (timer.unref) { timer.unref(); } return timer; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ucC5jb25uZWN0aW9ucG9vbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL25ldHdvcmtwcm94eS9jbGFzc2VzLm5wLmNvbm5lY3Rpb25wb29sLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sRUFBa0UsWUFBWSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFckg7O0dBRUc7QUFDSCxNQUFNLE9BQU8sY0FBYztJQUt6QixZQUFvQixPQUE2QjtRQUE3QixZQUFPLEdBQVAsT0FBTyxDQUFzQjtRQUp6QyxtQkFBYyxHQUF5QyxJQUFJLEdBQUcsRUFBRSxDQUFDO1FBQ2pFLHdCQUFtQixHQUF3QixJQUFJLEdBQUcsRUFBRSxDQUFDO1FBSTNELElBQUksQ0FBQyxNQUFNLEdBQUcsWUFBWSxDQUFDLE9BQU8sQ0FBQyxRQUFRLElBQUksTUFBTSxDQUFDLENBQUM7SUFDekQsQ0FBQztJQUVEOztPQUVHO0lBQ0ksYUFBYSxDQUFDLElBQVksRUFBRSxJQUFZO1FBQzdDLE9BQU8sSUFBSSxPQUFPLENBQUMsQ0FBQyxPQUFPLEVBQUUsTUFBTSxFQUFFLEVBQUU7WUFDckMsTUFBTSxPQUFPLEdBQUcsR0FBRyxJQUFJLElBQUksSUFBSSxFQUFFLENBQUM7WUFDbEMsTUFBTSxjQUFjLEdBQUcsSUFBSSxDQUFDLGNBQWMsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDO1lBRTlELDhCQUE4QjtZQUM5QixNQUFNLG1CQUFtQixHQUFHLGNBQWMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUM7WUFFcEUsSUFBSSxtQkFBbUIsSUFBSSxDQUFDLEVBQUUsQ0FBQztnQkFDN0Isb0NBQW9DO2dCQUNwQyxNQUFNLFVBQVUsR0FBRyxjQUFjLENBQUMsbUJBQW1CLENBQUMsQ0FBQztnQkFD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