@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
JavaScript
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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