UNPKG

@duongtrungnguyen/nestro

Version:
311 lines 13.5 kB
var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __decorateClass = (decorators, target, key, kind) => { var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target; for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result; if (kind && result) __defProp(target, key, result); return result; }; var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index); var http_proxy_service_exports = {}; __export(http_proxy_service_exports, { HttpProxyService: () => HttpProxyService }); module.exports = __toCommonJS(http_proxy_service_exports); var import_http = require("http"); var import_common = require("@nestjs/common"); var import_https = require("https"); var import_common2 = require("../../../common"); var import_utils = require("../utils"); var import_base_proxy = require("./base-proxy.service"); var import_constants = require("../constants"); var import_discovery = require("../../../discovery"); let HttpProxyService = class extends import_base_proxy.BaseProxyService { constructor(discoveryService) { super(discoveryService); } /** * Proxies an HTTP request to a target server. * Uses service discovery and load balancing if a service is specified, * or directly proxies to a specific target URL. * * @param req - Incoming request with raw body. * @param res - Express response object. * @param routeConfig - Configuration for the route to determine target or service. */ async proxyRequest(req, res, routeConfig) { this.validateRouteConfig(routeConfig); const proxyOptions = this.buildProxyOptions(routeConfig, { buffer: this.getRequestBuffer(req) }); const originalUrl = req.originalUrl || req.url; try { if (routeConfig.service) { await this.proxyToService(originalUrl, req, res, proxyOptions, routeConfig.service); } else { await this.proxyToTarget(originalUrl, req, res, proxyOptions, routeConfig.target); } } catch (error) { this.handleError(error, req, res, routeConfig.target); } } /** * Proxies an incoming HTTP request to a discovered service instance. * * This method uses the discovery service to find an available instance of the specified service, * then forwards the HTTP request to that instance using the provided proxy options. * If the connection to an instance fails, it will attempt to try another available instance. * Handles errors related to service discovery and proxying. * * @param originalUrl - The original URL of the incoming request. * @param req - The incoming HTTP request object, possibly containing the raw body. * @param res - The HTTP response object to send the proxied response. * @param proxyOptions - Options to configure the proxy behavior. * @param service - The name of the service to which the request should be proxied. * @returns A promise that resolves when the proxying operation is complete. * @throws Throws an error if service discovery fails or if proxying cannot be completed. */ async proxyToService(originalUrl, req, res, proxyOptions, service) { try { await this.discoveryService.discover(service, async (instance, tryAnotherInstance) => { const targetUrl = (0, import_common2.buildInstanceHttpUrl)(instance); (0, import_common2.debugLog)(HttpProxyService.name, `Proxying HTTP request from ${originalUrl} to ${targetUrl}`); await this.executeProxy(req, res, { ...proxyOptions, target: targetUrl }, this.handleProxy.bind(this), { onConnectFailed: (err) => { (0, import_common2.debugError)(HttpProxyService.name, `Connection failed to ${targetUrl}: ${err.message}`); tryAnotherInstance(); } }); }); } catch (error) { throw this.handleDiscoveryError(error); } } /** * Proxies an incoming HTTP request to the specified target URL using the provided proxy options. * * @param originalUrl - The original URL of the incoming request. * @param req - The incoming HTTP request object, potentially containing the raw body. * @param res - The HTTP response object to send the proxied response. * @param proxyOptions - Configuration options for the proxy operation. * @param targetUrl - The destination URL or URL object to which the request should be proxied. * @returns A promise that resolves when the proxy operation is complete. */ async proxyToTarget(originalUrl, req, res, proxyOptions, targetUrl) { (0, import_common2.debugLog)(HttpProxyService.name, `Proxying HTTP request from ${originalUrl} to ${targetUrl}`); await this.executeProxy(req, res, { ...proxyOptions, target: targetUrl }, this.handleProxy.bind(this)); } /** * Internal handler for setting up and streaming a proxy request. * * @param req - Incoming request. * @param res - Outgoing response. * @param options - Proxy configuration. * @param callbacks - Optional lifecycle event callbacks. */ handleProxy(req, res, options = {}, callbacks = {}) { const normalizedOptions = this.normalizeOptions(options, req.url || "/"); if ((req.method === "DELETE" || req.method === "OPTIONS") && !req.headers["content-length"]) { req.headers["content-length"] = "0"; delete req.headers["transfer-encoding"]; } if (normalizedOptions.timeout) { req.socket.setTimeout(normalizedOptions.timeout); } this.addForwardedHeaders(req, normalizedOptions.xfwd); this.streamRequest(req, res, normalizedOptions, callbacks); } /** * Streams the HTTP request to the actual target using http/https modules. * * @param req - Incoming request. * @param res - Outgoing response. * @param options - Normalized proxy options. * @param callbacks - Optional lifecycle callbacks. */ streamRequest(req, res, options, callbacks) { callbacks.onStart?.(req, res, options.target); const targetProtocol = options.target.protocol === "https:" ? import_https.request : import_http.request; const proxyOptions = (0, import_utils.setupOutgoing)({}, options, req); const proxyReq = targetProtocol(proxyOptions); this.handleProxyRequest(req, res, proxyReq, options, callbacks); this.sendRequest(req, proxyReq, options.buffer); } /** * Extracts the raw request buffer for proxying. * * @param req - Incoming request. * @returns Buffer, string, or undefined. */ getRequestBuffer(req) { if (req.rawBody) return req.rawBody; if (req.body && Object.keys(req.body).length > 0) { return JSON.stringify(req.body); } return void 0; } /** * Handles the lifecycle and events of a proxied HTTP request. * * Sets up event listeners on the proxy request and the original client request to manage errors, * timeouts, socket events, and responses. Invokes appropriate callbacks for connection failures, * proxy request events, and errors. Forwards the proxy response to the client. * * @param req - The incoming client HTTP request. * @param res - The server response object to send data back to the client. * @param proxyReq - The outgoing proxy HTTP request. * @param options - Proxy configuration options. * @param callbacks - Callback functions for handling proxy events. */ handleProxyRequest(req, res, proxyReq, options, callbacks) { proxyReq.on("error", (err) => { if (this.isConnectionError(err)) { callbacks.onConnectFailed?.(err); } this.handleError(err, req, res, options.target, callbacks.onError); }); proxyReq.on("socket", (socket) => { callbacks.onProxyReq?.(proxyReq, req, res, socket); }); if (options.proxyTimeout) { proxyReq.setTimeout(options.proxyTimeout, () => { const timeoutError = new Error("Proxy timeout"); proxyReq.destroy(timeoutError); callbacks.onConnectFailed?.(timeoutError); }); } req.on("aborted", () => { (0, import_common2.debugError)(HttpProxyService.name, "Client request aborted"); proxyReq.destroy(); }); req.on("error", (err) => { (0, import_common2.debugError)(HttpProxyService.name, `Client request error: ${err.message}`); proxyReq.destroy(); this.handleError(err, req, res, options.target, callbacks.onError); }); proxyReq.on("response", (proxyRes) => { this.handleProxyResponse(req, res, proxyRes, options, callbacks); }); } /** * Handles the response from the proxied server and pipes it to the client response. * * This method sets the status code and status message on the client response, * copies headers from the proxy response, rewrites cookies as necessary, and * streams the proxy response body to the client. It also invokes the appropriate * callbacks for proxy response and completion events, and handles errors that * may occur during the proxying process. * * @param req - The original incoming HTTP request from the client. * @param res - The HTTP response object to send data back to the client. * @param proxyRes - The HTTP response received from the proxied target server. * @param options - Proxy options containing configuration such as the target server. * @param callbacks - Callback functions for handling proxy events such as response, end, and error. */ handleProxyResponse(req, res, proxyRes, options, callbacks) { callbacks.onProxyRes?.(proxyRes, req, res); res.statusCode = proxyRes.statusCode || 500; if (proxyRes.statusMessage) { res.statusMessage = proxyRes.statusMessage; } this.copyHeaders(proxyRes, res); this.rewriteCookies(res, options); proxyRes.pipe(res); proxyRes.on("end", () => { callbacks.onEnd?.(req, res, proxyRes); (0, import_common2.debugLog)(HttpProxyService.name, `Proxy completed - ${proxyRes.statusCode}`); }); proxyRes.on("error", (err) => { if (!res.headersSent) { this.handleError(new Error(`Proxy response error: ${err.message}`), req, res, options.target, callbacks.onError); } else if (!res.finished) { res.end(); } }); } /** * Sends the request body to the proxy * * @param req - Client request * @param proxyReq - Proxy request * @param buffer - Optional buffer to send */ sendRequest(req, proxyReq, buffer) { if (buffer) { const data = Buffer.isBuffer(buffer) || typeof buffer === "string" ? buffer : JSON.stringify(buffer); proxyReq.write(data); proxyReq.end(); } else { req.pipe(proxyReq); } } /** * Checks if an error is related to connection issues * * @param err - The error to check * @returns boolean indicating if this is a connection error */ isConnectionError(err) { return import_constants.CONNECTION_ERROR_CODES.some((code) => err.message.includes(code) || err.code === code) || /connect|connection|timeout/i.test(err.message); } /** * Copies headers from proxy response to client response * * @param proxyRes - Proxy response * @param res - Client response */ copyHeaders(proxyRes, res) { const skipHeaders = ["connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailers", "transfer-encoding", "upgrade"]; for (const [key, value] of Object.entries(proxyRes.headers)) { if (value !== void 0 && !skipHeaders.includes(key.toLowerCase())) { try { res.setHeader(key, value); } catch (err) { (0, import_common2.debugWarn)(HttpProxyService.name, `Error setting header ${key}: ${err.message}`); } } } } /** * Optionally rewrites cookie domain and path in the response. * * @param res - Outgoing response. * @param options - Proxy configuration. */ rewriteCookies(res, options) { const cookies = res.getHeader("set-cookie"); if (!cookies) return; const rewrite = (config, property) => { const rewriteConfig = typeof config === "string" ? { "*": config } : config; res.setHeader("set-cookie", (0, import_utils.rewriteCookieProperty)(cookies, rewriteConfig, property)); }; if (options.cookieDomainRewrite) rewrite(options.cookieDomainRewrite, "domain"); if (options.cookiePathRewrite) rewrite(options.cookiePathRewrite, "path"); } }; HttpProxyService = __decorateClass([ (0, import_common.Injectable)(), __decorateParam(0, (0, import_common.Inject)(import_discovery.DiscoveryService)) ], HttpProxyService); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { HttpProxyService }); //# sourceMappingURL=http-proxy.service.js.map