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.

257 lines 23.1 kB
import * as plugins from '../../plugins.js'; import '../../core/models/socket-augmentation.js'; import { TemplateUtils } from '../../core/utils/template-utils.js'; /** * HTTP Request Handler Helper - handles requests with specific destinations * This is a helper class for the main RequestHandler */ export class HttpRequestHandler { /** * Handle HTTP request with a specific destination */ static async handleHttpRequestWithDestination(req, res, destination, routeContext, startTime, logger, metricsTracker, route) { try { // Apply URL rewriting if route config is provided if (route) { HttpRequestHandler.applyUrlRewriting(req, route, routeContext, logger); HttpRequestHandler.applyRouteHeaderModifications(route, req, res, logger); } // Create options for the proxy request const options = { hostname: destination.host, port: destination.port, path: req.url, method: req.method, headers: { ...req.headers } }; // Optionally rewrite host header to match target if (options.headers && 'host' in options.headers) { // Only apply if host header rewrite is enabled or not explicitly disabled const shouldRewriteHost = route?.action.options?.rewriteHostHeader !== false; if (shouldRewriteHost) { // Safely cast to OutgoingHttpHeaders to access host property options.headers.host = `${destination.host}:${destination.port}`; } } logger.debug(`Proxying request to ${destination.host}:${destination.port}${req.url}`, { method: req.method }); // Create proxy request const proxyReq = plugins.http.request(options, (proxyRes) => { // Copy status code res.statusCode = proxyRes.statusCode || 500; // Copy headers from proxy response to client response for (const [key, value] of Object.entries(proxyRes.headers)) { if (value !== undefined) { res.setHeader(key, value); } } // Apply response header modifications if route config is provided if (route && route.headers?.response) { HttpRequestHandler.applyResponseHeaderModifications(route, res, logger, routeContext); } // Pipe proxy response to client response proxyRes.pipe(res); // Increment served requests counter when the response finishes res.on('finish', () => { if (metricsTracker) { metricsTracker.incrementRequestsServed(); } // Log the completed request const duration = Date.now() - startTime; logger.debug(`Request completed in ${duration}ms: ${req.method} ${req.url} ${res.statusCode}`, { duration, statusCode: res.statusCode }); }); }); // Handle proxy request errors proxyReq.on('error', (error) => { const duration = Date.now() - startTime; logger.error(`Proxy error for ${req.method} ${req.url}: ${error.message}`, { duration, error: error.message }); // Increment failed requests counter if (metricsTracker) { metricsTracker.incrementFailedRequests(); } // Check if headers have already been sent if (!res.headersSent) { res.statusCode = 502; res.end(`Bad Gateway: ${error.message}`); } else { // If headers already sent, just close the connection res.end(); } }); // Pipe request body to proxy request and handle client-side errors req.pipe(proxyReq); // Handle client disconnection req.on('error', (error) => { logger.debug(`Client connection error: ${error.message}`); proxyReq.destroy(); // Increment failed requests counter on client errors if (metricsTracker) { metricsTracker.incrementFailedRequests(); } }); // Handle response errors res.on('error', (error) => { logger.debug(`Response error: ${error.message}`); proxyReq.destroy(); // Increment failed requests counter on response errors if (metricsTracker) { metricsTracker.incrementFailedRequests(); } }); } catch (error) { // Handle any unexpected errors logger.error(`Unexpected error handling request: ${error.message}`, { error: error.stack }); // Increment failed requests counter if (metricsTracker) { metricsTracker.incrementFailedRequests(); } if (!res.headersSent) { res.statusCode = 500; res.end('Internal Server Error'); } else { res.end(); } } } /** * Apply URL rewriting based on route configuration * Implements Phase 5.2: URL rewriting using route context * * @param req The request with the URL to rewrite * @param route The route configuration containing rewrite rules * @param routeContext Context for template variable resolution * @param logger Logger for debugging information * @returns True if URL was rewritten, false otherwise */ static applyUrlRewriting(req, route, routeContext, logger) { // Check if route has URL rewriting configuration if (!route.action.advanced?.urlRewrite) { return false; } const rewriteConfig = route.action.advanced.urlRewrite; // Store original URL for logging const originalUrl = req.url; if (rewriteConfig.pattern && rewriteConfig.target) { try { // Create a RegExp from the pattern with optional flags const regex = new RegExp(rewriteConfig.pattern, rewriteConfig.flags || ''); // Apply rewriting with template variable resolution let target = rewriteConfig.target; // Replace template variables in target with values from context target = TemplateUtils.resolveTemplateVariables(target, routeContext); // If onlyRewritePath is set, split URL into path and query parts if (rewriteConfig.onlyRewritePath && req.url) { const [path, query] = req.url.split('?'); const rewrittenPath = path.replace(regex, target); req.url = query ? `${rewrittenPath}?${query}` : rewrittenPath; } else { // Perform the replacement on the entire URL req.url = req.url?.replace(regex, target); } logger.debug(`URL rewritten: ${originalUrl} -> ${req.url}`); return true; } catch (err) { logger.error(`Error in URL rewriting: ${err}`); return false; } } return false; } /** * Apply header modifications from route configuration to request headers * Implements Phase 5.1: Route-based header manipulation for requests */ static applyRouteHeaderModifications(route, req, res, logger) { // Check if route has header modifications if (!route.headers) { return; } // Apply request header modifications (these will be sent to the backend) if (route.headers.request && req.headers) { // Create routing context for template resolution const routeContext = { domain: req.headers.host || '', path: req.url || '', clientIp: req.socket.remoteAddress?.replace('::ffff:', '') || '', serverIp: req.socket.localAddress?.replace('::ffff:', '') || '', port: parseInt(req.socket.localPort?.toString() || '0', 10), isTls: !!req.socket.encrypted, headers: req.headers, timestamp: Date.now(), connectionId: `${Date.now()}-${Math.floor(Math.random() * 10000)}`, }; for (const [key, value] of Object.entries(route.headers.request)) { // Skip if header already exists and we're not overriding if (req.headers[key.toLowerCase()] && !value.startsWith('!')) { continue; } // Handle special delete directive (!delete) if (value === '!delete') { delete req.headers[key.toLowerCase()]; logger.debug(`Deleted request header: ${key}`); continue; } // Handle forced override (!value) let finalValue; if (value.startsWith('!')) { // 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 req.headers[key.toLowerCase()] = finalValue; logger.debug(`Modified request header: ${key}=${finalValue}`); } } } /** * Apply header modifications from route configuration to response headers * Implements Phase 5.1: Route-based header manipulation for responses */ static applyResponseHeaderModifications(route, res, logger, routeContext) { // Check if route has response header modifications if (!route.headers?.response) { return; } // Apply response header modifications for (const [key, value] of Object.entries(route.headers.response)) { // Skip if header already exists and we're not overriding if (res.hasHeader(key) && !value.startsWith('!')) { continue; } // Handle special delete directive (!delete) if (value === '!delete') { res.removeHeader(key); logger.debug(`Deleted response header: ${key}`); 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 = routeContext ? '!' + TemplateUtils.resolveTemplateVariables(templateValue, routeContext) : '!' + templateValue; } else { // Resolve templates in the entire value finalValue = routeContext ? TemplateUtils.resolveTemplateVariables(value, routeContext) : value; } // Set the header res.setHeader(key, finalValue); logger.debug(`Modified response header: ${key}=${finalValue}`); } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHR0cC1yZXF1ZXN0LWhhbmRsZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9wcm94aWVzL2h0dHAtcHJveHkvaHR0cC1yZXF1ZXN0LWhhbmRsZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxrQkFBa0IsQ0FBQztBQUM1QyxPQUFPLDBDQUEwQyxDQUFDO0FBS2xELE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUVuRTs7O0dBR0c7QUFDSCxNQUFNLE9BQU8sa0JBQWtCO0lBQzdCOztPQUVHO0lBQ0ksTUFBTSxDQUFDLEtBQUssQ0FBQyxnQ0FBZ0MsQ0FDbEQsR0FBaUMsRUFDakMsR0FBZ0MsRUFDaEMsV0FBMkMsRUFDM0MsWUFBK0IsRUFDL0IsU0FBaUIsRUFDakIsTUFBZSxFQUNmLGNBQXVDLEVBQ3ZDLEtBQW9CO1FBRXBCLElBQUksQ0FBQztZQUNILGtEQUFrRDtZQUNsRCxJQUFJLEtBQUssRUFBRSxDQUFDO2dCQUNWLGtCQUFrQixDQUFDLGlCQUFpQixDQUFDLEdBQUcsRUFBRSxLQUFLLEVBQUUsWUFBWSxFQUFFLE1BQU0sQ0FBQyxDQUFDO2dCQUN2RSxrQkFBa0IsQ0FBQyw2QkFBNkIsQ0FBQyxLQUFLLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxNQUFNLENBQUMsQ0FBQztZQUM1RSxDQUFDO1lBRUQsdUNBQXVDO1lBQ3ZDLE1BQU0sT0FBTyxHQUFnQztnQkFDM0MsUUFBUSxFQUFFLFdBQVcsQ0FBQyxJQUFJO2dCQUMxQixJQUFJLEVBQUUsV0FBVyxDQUFDLElBQUk7Z0JBQ3RCLElBQUksRUFBRSxHQUFHLENBQUMsR0FBRztnQkFDYixNQUFNLEVBQUUsR0FBRyxDQUFDLE1BQU07Z0JBQ2xCLE9BQU8sRUFBRSxFQUFFLEdBQUcsR0FBRyxDQUFDLE9BQU8sRUFBRTthQUM1QixDQUFDO1lBRUYsaURBQWlEO1lBQ2pELElBQUksT0FBTyxDQUFDLE9B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