fix(networkproxy/requesthandler): Improve HTTP/2 request handling and error management in the proxy request handler; add try-catch around routing and update header processing to support per-backend protocol overrides.
This commit is contained in:
		| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@push.rocks/smartproxy', | ||||
|   version: '7.1.1', | ||||
|   version: '7.1.2', | ||||
|   description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.' | ||||
| } | ||||
|   | ||||
| @@ -132,23 +132,32 @@ export class RequestHandler { | ||||
|      | ||||
|     // Apply default headers | ||||
|     this.applyDefaultHeaders(res); | ||||
|     // If configured to proxy to backends over HTTP/2, use HTTP/2 client sessions | ||||
|     if (this.options.backendProtocol === 'http2') { | ||||
|       // Route and validate config | ||||
|       const proxyConfig = this.router.routeReq(req); | ||||
|       if (!proxyConfig) { | ||||
|         this.logger.warn(`No proxy configuration for host: ${req.headers.host}`); | ||||
|         res.statusCode = 404; | ||||
|         res.end('Not Found: No proxy configuration for this host'); | ||||
|         if (this.metricsTracker) this.metricsTracker.incrementFailedRequests(); | ||||
|         return; | ||||
|       } | ||||
|       // Determine backend target | ||||
|      | ||||
|     // Determine routing configuration | ||||
|     let proxyConfig: IReverseProxyConfig | undefined; | ||||
|     try { | ||||
|       proxyConfig = this.router.routeReq(req); | ||||
|     } catch (err) { | ||||
|       this.logger.error('Error routing request', err); | ||||
|       res.statusCode = 500; | ||||
|       res.end('Internal Server Error'); | ||||
|       if (this.metricsTracker) this.metricsTracker.incrementFailedRequests(); | ||||
|       return; | ||||
|     } | ||||
|     if (!proxyConfig) { | ||||
|       this.logger.warn(`No proxy configuration for host: ${req.headers.host}`); | ||||
|       res.statusCode = 404; | ||||
|       res.end('Not Found: No proxy configuration for this host'); | ||||
|       if (this.metricsTracker) this.metricsTracker.incrementFailedRequests(); | ||||
|       return; | ||||
|     } | ||||
|     // Determine protocol to backend (per-domain override or global) | ||||
|     const backendProto = proxyConfig.backendProtocol || this.options.backendProtocol; | ||||
|     if (backendProto === 'http2') { | ||||
|       const destination = this.connectionPool.getNextTarget( | ||||
|         proxyConfig.destinationIps, | ||||
|         proxyConfig.destinationPorts[0] | ||||
|       ); | ||||
|       // Obtain or create HTTP/2 session | ||||
|       const key = `${destination.host}:${destination.port}`; | ||||
|       let session = this.h2Sessions.get(key); | ||||
|       if (!session || session.closed || (session as any).destroyed) { | ||||
| @@ -158,40 +167,30 @@ export class RequestHandler { | ||||
|         session.on('close', () => this.h2Sessions.delete(key)); | ||||
|       } | ||||
|       // Build headers for HTTP/2 request | ||||
|       const h2Headers: Record<string, string> = { | ||||
|         ':method': req.method || 'GET', | ||||
|         ':path': req.url || '/', | ||||
|       const hdrs: Record<string, any> = { | ||||
|         ':method': req.method, | ||||
|         ':path': req.url, | ||||
|         ':authority': `${destination.host}:${destination.port}` | ||||
|       }; | ||||
|       for (const [k, v] of Object.entries(req.headers)) { | ||||
|         if (typeof v === 'string' && !k.startsWith(':')) { | ||||
|           h2Headers[k] = v; | ||||
|         } | ||||
|       for (const [hk, hv] of Object.entries(req.headers)) { | ||||
|         if (typeof hv === 'string') hdrs[hk] = hv; | ||||
|       } | ||||
|       // Open HTTP/2 stream | ||||
|       const h2Stream = session.request(h2Headers); | ||||
|       // Pipe client request body to backend | ||||
|       const h2Stream = session.request(hdrs); | ||||
|       req.pipe(h2Stream); | ||||
|       // Handle backend response | ||||
|       h2Stream.on('response', (headers, flags) => { | ||||
|         const status = headers[':status'] as number || 502; | ||||
|         // Map headers | ||||
|         for (const [hk, hv] of Object.entries(headers)) { | ||||
|           if (!hk.startsWith(':') && hv) { | ||||
|             res.setHeader(hk, hv as string); | ||||
|       h2Stream.on('response', (hdrs2: any) => { | ||||
|         const status = (hdrs2[':status'] as number) || 502; | ||||
|         res.statusCode = status; | ||||
|         // Copy headers from HTTP/2 response to HTTP/1 response | ||||
|         for (const [hk, hv] of Object.entries(hdrs2)) { | ||||
|           if (!hk.startsWith(':') && hv != null) { | ||||
|             res.setHeader(hk, hv as string | string[]); | ||||
|           } | ||||
|         } | ||||
|         res.statusCode = status; | ||||
|         h2Stream.pipe(res); | ||||
|       }); | ||||
|       h2Stream.on('error', (err) => { | ||||
|         this.logger.error(`HTTP/2 proxy error: ${err.message}`); | ||||
|         if (!res.headersSent) { | ||||
|           res.statusCode = 502; | ||||
|           res.end(`Bad Gateway: ${err.message}`); | ||||
|         } else { | ||||
|           res.end(); | ||||
|         } | ||||
|         res.statusCode = 502; | ||||
|         res.end(`Bad Gateway: ${err.message}`); | ||||
|         if (this.metricsTracker) this.metricsTracker.incrementFailedRequests(); | ||||
|       }); | ||||
|       return; | ||||
|   | ||||
| @@ -60,6 +60,11 @@ export interface IReverseProxyConfig { | ||||
|     pass: string; | ||||
|   }; | ||||
|   rewriteHostHeader?: boolean; | ||||
|   /** | ||||
|    * Protocol to use when proxying to this backend: 'http1' or 'http2'. | ||||
|    * Overrides the global backendProtocol option if set. | ||||
|    */ | ||||
|   backendProtocol?: 'http1' | 'http2'; | ||||
| } | ||||
|  | ||||
| /** | ||||
|   | ||||
		Reference in New Issue
	
	Block a user