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.

201 lines 18.6 kB
import * as plugins from '../../plugins.js'; /** * HTTP/2 Request Handler Helper - handles HTTP/2 streams with specific destinations * This is a helper class for the main RequestHandler */ export class Http2RequestHandler { /** * Handle HTTP/2 stream with direct HTTP/2 backend */ static async handleHttp2WithHttp2Destination(stream, headers, destination, routeContext, sessions, logger, metricsTracker) { const key = `${destination.host}:${destination.port}`; // Get or create a client HTTP/2 session let session = sessions.get(key); if (!session || session.closed || session.destroyed) { try { // Connect to the backend HTTP/2 server session = plugins.http2.connect(`http://${destination.host}:${destination.port}`); sessions.set(key, session); // Handle session errors and cleanup session.on('error', (err) => { logger.error(`HTTP/2 session error to ${key}: ${err.message}`); sessions.delete(key); }); session.on('close', () => { logger.debug(`HTTP/2 session closed to ${key}`); sessions.delete(key); }); } catch (err) { logger.error(`Failed to establish HTTP/2 session to ${key}: ${err.message}`); stream.respond({ ':status': 502 }); stream.end('Bad Gateway: Failed to establish connection to backend'); if (metricsTracker) metricsTracker.incrementFailedRequests(); return; } } try { // Build headers for backend HTTP/2 request const h2Headers = { ':method': headers[':method'], ':path': headers[':path'], ':authority': `${destination.host}:${destination.port}` }; // Copy other headers, excluding pseudo-headers for (const [key, value] of Object.entries(headers)) { if (!key.startsWith(':') && typeof value === 'string') { h2Headers[key] = value; } } logger.debug(`Proxying HTTP/2 request to ${destination.host}:${destination.port}${headers[':path']}`, { method: headers[':method'] }); // Create HTTP/2 request stream to the backend const h2Stream = session.request(h2Headers); // Pipe client stream to backend stream stream.pipe(h2Stream); // Handle responses from the backend h2Stream.on('response', (responseHeaders) => { // Map status and headers to client response const resp = { ':status': responseHeaders[':status'] }; // Copy non-pseudo headers for (const [key, value] of Object.entries(responseHeaders)) { if (!key.startsWith(':') && value !== undefined) { resp[key] = value; } } // Send headers to client stream.respond(resp); // Pipe backend response to client h2Stream.pipe(stream); // Track successful requests stream.on('end', () => { if (metricsTracker) metricsTracker.incrementRequestsServed(); logger.debug(`HTTP/2 request completed: ${headers[':method']} ${headers[':path']} ${responseHeaders[':status']}`, { method: headers[':method'], status: responseHeaders[':status'] }); }); }); // Handle backend errors h2Stream.on('error', (err) => { logger.error(`HTTP/2 stream error: ${err.message}`); // Only send error response if headers haven't been sent if (!stream.headersSent) { stream.respond({ ':status': 502 }); stream.end(`Bad Gateway: ${err.message}`); } else { stream.end(); } if (metricsTracker) metricsTracker.incrementFailedRequests(); }); // Handle client stream errors stream.on('error', (err) => { logger.debug(`Client HTTP/2 stream error: ${err.message}`); h2Stream.destroy(); if (metricsTracker) metricsTracker.incrementFailedRequests(); }); } catch (err) { logger.error(`Error handling HTTP/2 request: ${err.message}`); // Only send error response if headers haven't been sent if (!stream.headersSent) { stream.respond({ ':status': 500 }); stream.end('Internal Server Error'); } else { stream.end(); } if (metricsTracker) metricsTracker.incrementFailedRequests(); } } /** * Handle HTTP/2 stream with HTTP/1 backend */ static async handleHttp2WithHttp1Destination(stream, headers, destination, routeContext, logger, metricsTracker) { try { // Build headers for HTTP/1 proxy request, excluding HTTP/2 pseudo-headers const outboundHeaders = {}; for (const [key, value] of Object.entries(headers)) { if (typeof key === 'string' && typeof value === 'string' && !key.startsWith(':')) { outboundHeaders[key] = value; } } // Always rewrite host header to match target outboundHeaders.host = `${destination.host}:${destination.port}`; logger.debug(`Proxying HTTP/2 request to HTTP/1 backend ${destination.host}:${destination.port}${headers[':path']}`, { method: headers[':method'] }); // Create HTTP/1 proxy request const proxyReq = plugins.http.request({ hostname: destination.host, port: destination.port, path: headers[':path'], method: headers[':method'], headers: outboundHeaders }, (proxyRes) => { // Map status and headers back to HTTP/2 const responseHeaders = { ':status': proxyRes.statusCode || 500 }; // Copy headers from HTTP/1 response to HTTP/2 response for (const [key, value] of Object.entries(proxyRes.headers)) { if (value !== undefined) { responseHeaders[key] = value; } } // Send headers to client stream.respond(responseHeaders); // Pipe HTTP/1 response to HTTP/2 stream proxyRes.pipe(stream); // Clean up when client disconnects stream.on('close', () => proxyReq.destroy()); stream.on('error', () => proxyReq.destroy()); // Track successful requests stream.on('end', () => { if (metricsTracker) metricsTracker.incrementRequestsServed(); logger.debug(`HTTP/2 to HTTP/1 request completed: ${headers[':method']} ${headers[':path']} ${proxyRes.statusCode}`, { method: headers[':method'], status: proxyRes.statusCode }); }); }); // Handle proxy request errors proxyReq.on('error', (err) => { logger.error(`HTTP/1 proxy error: ${err.message}`); // Only send error response if headers haven't been sent if (!stream.headersSent) { stream.respond({ ':status': 502 }); stream.end(`Bad Gateway: ${err.message}`); } else { stream.end(); } if (metricsTracker) metricsTracker.incrementFailedRequests(); }); // Pipe client stream to proxy request stream.pipe(proxyReq); // Handle client stream errors stream.on('error', (err) => { logger.debug(`Client HTTP/2 stream error: ${err.message}`); proxyReq.destroy(); if (metricsTracker) metricsTracker.incrementFailedRequests(); }); } catch (err) { logger.error(`Error handling HTTP/2 to HTTP/1 request: ${err.message}`); // Only send error response if headers haven't been sent if (!stream.headersSent) { stream.respond({ ':status': 500 }); stream.end('Internal Server Error'); } else { stream.end(); } if (metricsTracker) metricsTracker.incrementFailedRequests(); } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHR0cDItcmVxdWVzdC1oYW5kbGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvcHJveGllcy9odHRwLXByb3h5L2h0dHAyLXJlcXVlc3QtaGFuZGxlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBSzVDOzs7R0FHRztBQUNILE1BQU0sT0FBTyxtQkFBbUI7SUFDOUI7O09BRUc7SUFDSSxNQUFNLENBQUMsS0FBSyxDQUFDLCtCQUErQixDQUNqRCxNQUF1QyxFQUN2QyxPQUEwQyxFQUMxQyxXQUEyQyxFQUMzQyxZQUErQixFQUMvQixRQUF1RCxFQUN2RCxNQUFlLEVBQ2YsY0FBdUM7UUFFdkMsTUFBTSxHQUFHLEdBQUcsR0FBRyxXQUFXLENBQUMsSUFBSSxJQUFJLFdBQVcsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUV0RCx3Q0FBd0M7UUFDeEMsSUFBSSxPQUFPLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNoQyxJQUFJLENBQUMsT0FBTyxJQUFJLE9BQU8sQ0FBQyxNQUFNLElBQUssT0FBZSxDQUFDLFNBQVMsRUFBRSxDQUFDO1lBQzdELElBQUksQ0FBQztnQkFDSCx1Q0FBdUM7Z0JBQ3ZDLE9BQU8sR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxVQUFVLFdBQVcsQ0FBQyxJQUFJLElBQUksV0FBVyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUM7Z0JBQ2xGLFFBQVEsQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLE9BQU8sQ0FBQyxDQUFDO2dCQUUzQixvQ0FBb0M7Z0JBQ3BDLE9BQU8sQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLENBQUMsR0FBRyxFQUFFLEVBQUU7b0JBQzFCLE1BQU0sQ0FBQyxLQUFLLENBQUMsMkJBQTJCLEdBQUcsS0FBSyxHQUFHLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztvQkFDL0Qs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