@duongtrungnguyen/nestro
Version:
Service registry for Nest JS
311 lines • 13.5 kB
JavaScript
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