import * as plugins from '../../plugins.js'; import { ForwardingHandler } from './base-handler.js'; import type { IForwardConfig } from '../config/forwarding-types.js'; import { ForwardingHandlerEvents } from '../config/forwarding-types.js'; /** * Handler for HTTP-only forwarding */ export class HttpForwardingHandler extends ForwardingHandler { /** * Create a new HTTP forwarding handler * @param config The forwarding configuration */ constructor(config: IForwardConfig) { super(config); // Validate that this is an HTTP-only configuration if (config.type !== 'http-only') { throw new Error(`Invalid configuration type for HttpForwardingHandler: ${config.type}`); } } /** * Initialize the handler * HTTP handler doesn't need special initialization */ public async initialize(): Promise { // Basic initialization from parent class await super.initialize(); } /** * Handle a raw socket connection * HTTP handler doesn't do much with raw sockets as it mainly processes * parsed HTTP requests */ public handleConnection(socket: plugins.net.Socket): void { // For HTTP, we mainly handle parsed requests, but we can still set up // some basic connection tracking const remoteAddress = socket.remoteAddress || 'unknown'; socket.on('close', (hadError) => { this.emit(ForwardingHandlerEvents.DISCONNECTED, { remoteAddress, hadError }); }); socket.on('error', (error) => { this.emit(ForwardingHandlerEvents.ERROR, { remoteAddress, error: error.message }); }); this.emit(ForwardingHandlerEvents.CONNECTED, { remoteAddress }); } /** * Handle an HTTP request * @param req The HTTP request * @param res The HTTP response */ public handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void { // Get the target from configuration const target = this.getTargetFromConfig(); // Create a custom headers object with variables for substitution const variables = { clientIp: req.socket.remoteAddress || 'unknown' }; // Prepare headers, merging with any custom headers from config const headers = this.applyCustomHeaders(req.headers, variables); // Create the proxy request options const options = { hostname: target.host, port: target.port, path: req.url, method: req.method, headers }; // Create the proxy request const proxyReq = plugins.http.request(options, (proxyRes) => { // Copy status code and headers from the proxied response res.writeHead(proxyRes.statusCode || 500, proxyRes.headers); // Pipe the proxy response to the client response proxyRes.pipe(res); // Track bytes for logging let responseSize = 0; proxyRes.on('data', (chunk) => { responseSize += chunk.length; }); proxyRes.on('end', () => { this.emit(ForwardingHandlerEvents.HTTP_RESPONSE, { statusCode: proxyRes.statusCode, headers: proxyRes.headers, size: responseSize }); }); }); // Handle errors in the proxy request proxyReq.on('error', (error) => { this.emit(ForwardingHandlerEvents.ERROR, { remoteAddress: req.socket.remoteAddress, error: `Proxy request error: ${error.message}` }); // Send an error response if headers haven't been sent yet if (!res.headersSent) { res.writeHead(502, { 'Content-Type': 'text/plain' }); res.end(`Error forwarding request: ${error.message}`); } else { // Just end the response if headers have already been sent res.end(); } }); // Track request details for logging let requestSize = 0; req.on('data', (chunk) => { requestSize += chunk.length; }); // Log the request this.emit(ForwardingHandlerEvents.HTTP_REQUEST, { method: req.method, url: req.url, headers: req.headers, remoteAddress: req.socket.remoteAddress, target: `${target.host}:${target.port}` }); // Pipe the client request to the proxy request if (req.readable) { req.pipe(proxyReq); } else { proxyReq.end(); } } }