UNPKG

@push.rocks/smartproxy

Version:

A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.

505 lines 48.9 kB
import * as plugins from '../../plugins.js'; import '../../core/models/socket-augmentation.js'; import { createLogger } from './models/types.js'; import { ConnectionPool } from './connection-pool.js'; import { HttpRouter } from '../../routing/router/index.js'; import { toBaseContext } from '../../core/models/route-context.js'; import { ContextCreator } from './context-creator.js'; import { SecurityManager } from './security-manager.js'; import { TemplateUtils } from '../../core/utils/template-utils.js'; import { getMessageSize, toBuffer } from '../../core/utils/websocket-utils.js'; /** * Handles WebSocket connections and proxying */ export class WebSocketHandler { constructor(options, connectionPool, routes = []) { this.options = options; this.connectionPool = connectionPool; this.routes = routes; this.heartbeatInterval = null; this.wsServer = null; this.contextCreator = new ContextCreator(); this.router = null; this.logger = createLogger(options.logLevel || 'info'); this.securityManager = new SecurityManager(this.logger, routes); // Initialize router if we have routes if (routes.length > 0) { this.router = new HttpRouter(routes, this.logger); } } /** * Set the route configurations */ setRoutes(routes) { this.routes = routes; // Initialize or update the route router if (!this.router) { this.router = new HttpRouter(routes, this.logger); } else { this.router.setRoutes(routes); } // Update the security manager this.securityManager.setRoutes(routes); } /** * Select the appropriate target from the targets array based on sub-matching criteria */ selectTarget(targets, context) { // Sort targets by priority (higher first) const sortedTargets = [...targets].sort((a, b) => (b.priority || 0) - (a.priority || 0)); // Find the first matching target for (const target of sortedTargets) { if (!target.match) { // No match criteria means this is a default/fallback target return target; } // Check port match if (target.match.ports && !target.match.ports.includes(context.port)) { continue; } // Check path match (supports wildcards) if (target.match.path && context.path) { const pathPattern = target.match.path.replace(/\*/g, '.*'); const pathRegex = new RegExp(`^${pathPattern}$`); if (!pathRegex.test(context.path)) { continue; } } // Check method match if (target.match.method && context.method && !target.match.method.includes(context.method)) { continue; } // Check headers match if (target.match.headers && context.headers) { let headersMatch = true; for (const [key, pattern] of Object.entries(target.match.headers)) { const headerValue = context.headers[key.toLowerCase()]; if (!headerValue) { headersMatch = false; break; } if (pattern instanceof RegExp) { if (!pattern.test(headerValue)) { headersMatch = false; break; } } else if (headerValue !== pattern) { headersMatch = false; break; } } if (!headersMatch) { continue; } } // All criteria matched return target; } // No matching target found, return the first target without match criteria (default) return sortedTargets.find(t => !t.match) || null; } /** * Initialize WebSocket server on an existing HTTPS server */ initialize(server) { // Create WebSocket server this.wsServer = new plugins.ws.WebSocketServer({ server: server, clientTracking: true }); // Handle WebSocket connections this.wsServer.on('connection', (wsIncoming, req) => { this.handleWebSocketConnection(wsIncoming, req); }); // Start the heartbeat interval this.startHeartbeat(); this.logger.info('WebSocket handler initialized'); } /** * Start the heartbeat interval to check for inactive WebSocket connections */ startHeartbeat() { // Clean up existing interval if any if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); } // Set up the heartbeat interval (check every 30 seconds) this.heartbeatInterval = setInterval(() => { if (!this.wsServer || this.wsServer.clients.size === 0) { return; // Skip if no active connections } this.logger.debug(`WebSocket heartbeat check for ${this.wsServer.clients.size} clients`); this.wsServer.clients.forEach((ws) => { const wsWithHeartbeat = ws; if (wsWithHeartbeat.isAlive === false) { this.logger.debug('Terminating inactive WebSocket connection'); return wsWithHeartbeat.terminate(); } wsWithHeartbeat.isAlive = false; wsWithHeartbeat.ping(); }); }, 30000); // Make sure the interval doesn't keep the process alive if (this.heartbeatInterval.unref) { this.heartbeatInterval.unref(); } } /** * Handle a new WebSocket connection */ handleWebSocketConnection(wsIncoming, req) { this.logger.debug(`WebSocket connection initiated from ${req.headers.host}`); try { // Initialize heartbeat tracking wsIncoming.isAlive = true; wsIncoming.lastPong = Date.now(); // Handle pong messages to track liveness wsIncoming.on('pong', () => { wsIncoming.isAlive = true; wsIncoming.lastPong = Date.now(); }); // Create a context for routing const connectionId = `ws-${Date.now()}-${Math.floor(Math.random() * 10000)}`; const routeContext = this.contextCreator.createHttpRouteContext(req, { connectionId, clientIp: req.socket.remoteAddress?.replace('::ffff:', '') || '0.0.0.0', serverIp: req.socket.localAddress?.replace('::ffff:', '') || '0.0.0.0', tlsVersion: req.socket.getTLSVersion?.() || undefined }); // Try modern router first if available let route; if (this.router) { route = this.router.routeReq(req); } // Define destination variables let destination; // If we found a route with the modern router, use it if (route && route.action.type === 'forward' && route.action.targets && route.action.targets.length > 0) { this.logger.debug(`Found matching WebSocket route: ${route.name || 'unnamed'}`); // Select the appropriate target from the targets array const selectedTarget = this.selectTarget(route.action.targets, { port: routeContext.port, path: routeContext.path, headers: routeContext.headers, method: routeContext.method }); if (!selectedTarget) { this.logger.error(`No matching target found for route ${route.name}`); wsIncoming.close(1003, 'No matching target'); return; } // Check if WebSockets are enabled for this route if (route.action.websocket?.enabled === false) { this.logger.debug(`WebSockets are disabled for route: ${route.name || 'unnamed'}`); wsIncoming.close(1003, 'WebSockets not supported for this route'); return; } // Check security restrictions if configured to authenticate WebSocket requests if (route.action.websocket?.authenticateRequest !== false && route.security) { if (!this.securityManager.isAllowed(route, toBaseContext(routeContext))) { this.logger.warn(`WebSocket connection denied by security policy for ${routeContext.clientIp}`); wsIncoming.close(1008, 'Access denied by security policy'); return; } // Check origin restrictions if configured const origin = req.headers.origin; if (origin && route.action.websocket?.allowedOrigins && route.action.websocket.allowedOrigins.length > 0) { const isAllowed = route.action.websocket.allowedOrigins.some(allowedOrigin => { // Handle wildcards and template variables if (allowedOrigin.includes('*') || allowedOrigin.includes('{')) { const pattern = allowedOrigin.replace(/\*/g, '.*'); const resolvedPattern = TemplateUtils.resolveTemplateVariables(pattern, routeContext); const regex = new RegExp(`^${resolvedPattern}$`); return regex.test(origin); } return allowedOrigin === origin; }); if (!isAllowed) { this.logger.warn(`WebSocket origin ${origin} not allowed for route: ${route.name || 'unnamed'}`); wsIncoming.close(1008, 'Origin not allowed'); return; } } } // Extract target information, resolving functions if needed let targetHost; let targetPort; try { // Resolve host if it's a function if (typeof selectedTarget.host === 'function') { const resolvedHost = selectedTarget.host(toBaseContext(routeContext)); targetHost = resolvedHost; this.logger.debug(`Resolved function-based host for WebSocket: ${Array.isArray(resolvedHost) ? resolvedHost.join(', ') : resolvedHost}`); } else { targetHost = selectedTarget.host; } // Resolve port if it's a function if (typeof selectedTarget.port === 'function') { targetPort = selectedTarget.port(toBaseContext(routeContext)); this.logger.debug(`Resolved function-based port for WebSocket: ${targetPort}`); } else { targetPort = selectedTarget.port === 'preserve' ? routeContext.port : selectedTarget.port; } // Select a single host if an array was provided const selectedHost = Array.isArray(targetHost) ? targetHost[Math.floor(Math.random() * targetHost.length)] : targetHost; // Create a destination for the WebSocket connection destination = { host: selectedHost, port: targetPort }; this.logger.debug(`WebSocket destination resolved: ${selectedHost}:${targetPort}`); } catch (err) { this.logger.error(`Error evaluating function-based target for WebSocket: ${err}`); wsIncoming.close(1011, 'Internal server error'); return; } } else { // No route found this.logger.warn(`No route configuration for WebSocket host: ${req.headers.host}`); wsIncoming.close(1008, 'No route configuration for this host'); return; } // Build target URL with potential path rewriting // Determine protocol based on the target's configuration // For WebSocket connections, we use ws for HTTP backends and wss for HTTPS backends const isTargetSecure = destination.port === 443; const protocol = isTargetSecure ? 'wss' : 'ws'; let targetPath = req.url || '/'; // Apply path rewriting if configured if (route?.action.websocket?.rewritePath) { const originalPath = targetPath; targetPath = TemplateUtils.resolveTemplateVariables(route.action.websocket.rewritePath, { ...routeContext, path: targetPath }); this.logger.debug(`WebSocket path rewritten: ${originalPath} -> ${targetPath}`); } const targetUrl = `${protocol}://${destination.host}:${destination.port}${targetPath}`; this.logger.debug(`WebSocket connection from ${req.socket.remoteAddress} to ${targetUrl}`); // Create headers for outgoing WebSocket connection const headers = {}; // Copy relevant headers from incoming request for (const [key, value] of Object.entries(req.headers)) { if (value && typeof value === 'string' && key.toLowerCase() !== 'connection' && key.toLowerCase() !== 'upgrade' && key.toLowerCase() !== 'sec-websocket-key' && key.toLowerCase() !== 'sec-websocket-version') { headers[key] = value; } } // Always rewrite host header for WebSockets for consistency headers['host'] = `${destination.host}:${destination.port}`; // Add custom headers from route configuration if (route?.action.websocket?.customHeaders) { for (const [key, value] of Object.entries(route.action.websocket.customHeaders)) { // Skip if header already exists and we're not overriding if (headers[key.toLowerCase()] && !value.startsWith('!')) { continue; } // Handle special delete directive (!delete) if (value === '!delete') { delete headers[key.toLowerCase()]; continue; } // Handle forced override (!value) let finalValue; if (value.startsWith('!') && value !== '!delete') { // Keep the ! but resolve any templates in the rest const templateValue = value.substring(1); finalValue = '!' + TemplateUtils.resolveTemplateVariables(templateValue, routeContext); } else { // Resolve templates in the entire value finalValue = TemplateUtils.resolveTemplateVariables(value, routeContext); } // Set the header headers[key.toLowerCase()] = finalValue; } } // Create WebSocket connection options const wsOptions = { headers: headers, followRedirects: true }; // Add subprotocols if configured if (route?.action.websocket?.subprotocols && route.action.websocket.subprotocols.length > 0) { wsOptions.protocols = route.action.websocket.subprotocols; } else if (req.headers['sec-websocket-protocol']) { // Pass through client requested protocols wsOptions.protocols = req.headers['sec-websocket-protocol'].split(',').map(p => p.trim()); } // Create outgoing WebSocket connection this.logger.debug(`Creating WebSocket connection to ${targetUrl} with options:`, { headers: wsOptions.headers, protocols: wsOptions.protocols }); const wsOutgoing = new plugins.wsDefault(targetUrl, wsOptions); this.logger.debug(`WebSocket instance created, waiting for connection...`); // Handle connection errors wsOutgoing.on('error', (err) => { this.logger.error(`WebSocket target connection error: ${err.message}`); if (wsIncoming.readyState === wsIncoming.OPEN) { wsIncoming.close(1011, 'Internal server error'); } }); // Handle outgoing connection open wsOutgoing.on('open', () => { this.logger.debug(`WebSocket target connection opened to ${targetUrl}`); // Set up custom ping interval if configured let pingInterval = null; if (route?.action.websocket?.pingInterval && route.action.websocket.pingInterval > 0) { pingInterval = setInterval(() => { if (wsIncoming.readyState === wsIncoming.OPEN) { wsIncoming.ping(); this.logger.debug(`Sent WebSocket ping to client for route: ${route.name || 'unnamed'}`); } }, route.action.websocket.pingInterval); // Don't keep process alive just for pings if (pingInterval.unref) pingInterval.unref(); } // Set up custom ping timeout if configured let pingTimeout = null; const pingTimeoutMs = route?.action.websocket?.pingTimeout || 60000; // Default 60s // Define timeout function for cleaner code const resetPingTimeout = () => { if (pingTimeout) clearTimeout(pingTimeout); pingTimeout = setTimeout(() => { this.logger.debug(`WebSocket ping timeout for client connection on route: ${route?.name || 'unnamed'}`); wsIncoming.terminate(); }, pingTimeoutMs); // Don't keep process alive just for timeouts if (pingTimeout.unref) pingTimeout.unref(); }; // Reset timeout on pong wsIncoming.on('pong', () => { wsIncoming.isAlive = true; wsIncoming.lastPong = Date.now(); resetPingTimeout(); }); // Initial ping timeout resetPingTimeout(); // Handle potential message size limits const maxSize = route?.action.websocket?.maxPayloadSize || 0; // Forward incoming messages to outgoing connection wsIncoming.on('message', (data, isBinary) => { this.logger.debug(`WebSocket forwarding message from client to target: ${data.toString()}`); if (wsOutgoing.readyState === wsOutgoing.OPEN) { // Check message size if limit is set const messageSize = getMessageSize(data); if (maxSize > 0 && messageSize > maxSize) { this.logger.warn(`WebSocket message exceeds max size (${messageSize} > ${maxSize})`); wsIncoming.close(1009, 'Message too big'); return; } wsOutgoing.send(data, { binary: isBinary }); } else { this.logger.warn(`WebSocket target connection not open (state: ${wsOutgoing.readyState})`); } }); // Forward outgoing messages to incoming connection wsOutgoing.on('message', (data, isBinary) => { this.logger.debug(`WebSocket forwarding message from target to client: ${data.toString()}`); if (wsIncoming.readyState === wsIncoming.OPEN) { wsIncoming.send(data, { binary: isBinary }); } else { this.logger.warn(`WebSocket client connection not open (state: ${wsIncoming.readyState})`); } }); // Handle closing of connections wsIncoming.on('close', (code, reason) => { this.logger.debug(`WebSocket client connection closed: ${code} ${reason}`); if (wsOutgoing.readyState === wsOutgoing.OPEN) { // Ensure code is a valid WebSocket close code number const validCode = typeof code === 'number' && code >= 1000 && code <= 4999 ? code : 1000; try { const reasonString = reason ? toBuffer(reason).toString() : ''; wsOutgoing.close(validCode, reasonString); } catch (err) { this.logger.error('Error closing wsOutgoing:', err); wsOutgoing.close(validCode); } } // Clean up timers if (pingInterval) clearInterval(pingInterval); if (pingTimeout) clearTimeout(pingTimeout); }); wsOutgoing.on('close', (code, reason) => { this.logger.debug(`WebSocket target connection closed: ${code} ${reason}`); if (wsIncoming.readyState === wsIncoming.OPEN) { // Ensure code is a valid WebSocket close code number const validCode = typeof code === 'number' && code >= 1000 && code <= 4999 ? code : 1000; try { const reasonString = reason ? toBuffer(reason).toString() : ''; wsIncoming.close(validCode, reasonString); } catch (err) { this.logger.error('Error closing wsIncoming:', err); wsIncoming.close(validCode); } } // Clean up timers if (pingInterval) clearInterval(pingInterval); if (pingTimeout) clearTimeout(pingTimeout); }); this.logger.debug(`WebSocket connection established: ${req.headers.host} -> ${destination.host}:${destination.port}`); }); } catch (error) { this.logger.error(`Error handling WebSocket connection: ${error.message}`); if (wsIncoming.readyState === wsIncoming.OPEN) { wsIncoming.close(1011, 'Internal server error'); } } } /** * Get information about active WebSocket connections */ getConnectionInfo() { return { activeConnections: this.wsServer ? this.wsServer.clients.size : 0 }; } /** * Shutdown the WebSocket handler */ shutdown() { // Stop heartbeat interval if (this.heartbeatInterval) { clearInterval(this.heartbeatInterval); this.heartbeatInterval = null; } // Close all WebSocket connections if (this.wsServer) { this.logger.info(`Closing ${this.wsServer.clients.size} WebSocket connections`); for (const client of this.wsServer.clients) { try { client.terminate(); } catch (error) { this.logger.error('Error terminating WebSocket client', error); } } // Close the server this.wsServer.close(); this.wsServer = null; } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2Vic29ja2V0LWhhbmRsZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9wcm94aWVzL2h0dHAtcHJveHkvd2Vic29ja2V0LWhhbmRsZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxrQkFBa0IsQ0FBQztBQUM1QyxPQUFPLDBDQUEwQyxDQUFDO0FBQ2xELE9BQU8sRUFBc0UsWUFBWSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDckgsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ3RELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUczRCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sb0NBQW9DLENBQUM7QUFDbkUsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ3RELE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUN4RCxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sb0NBQW9DLENBQUM7QUFDbkUsT0FBTyxFQUFFLGNBQWMsRUFBRSxRQUFRLEVBQUUsTUFBTSxxQ0FBcUMsQ0FBQztBQUUvRTs7R0FFRztBQUNILE1BQU0sT0FBTyxnQkFBZ0I7SUFRM0IsWUFDVSxPQUEwQixFQUMxQixjQUE4QixFQUM5QixTQUF5QixFQUFFO1FBRjNCLFlBQU8sR0FBUCxPQUFPLENBQW1CO1FBQzFCLG1CQUFjLEdBQWQsY0FBYyxDQUFnQjtRQUM5QixXQUFNLEdBQU4sTUFBTSxDQUFxQjtRQVY3QixzQkFBaUIsR0FBMEIsSUFBSSxDQUFDO1FBQ2hELGFBQVEsR0FBc0MsSUFBSSxDQUFDO1FBRW5ELG1CQUFjLEdBQW1CLElBQUksY0FBYyxFQUFFLENBQUM7UUFDdEQsV0FBTSxHQUFzQixJQUFJLENBQUM7UUFRdkMsSUFBSSxDQUFDLE1BQU0sR0FBRyxZQUFZLENBQUMsT0FBTyxDQUFDLFFBQVEsSUFB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