From 1b6d1c74a18fb3608db7a159f3595372adc50b4f Mon Sep 17 00:00:00 2001 From: SrGooglo Date: Mon, 12 May 2025 02:26:40 +0000 Subject: [PATCH] Add support for extra proxies via external file Allows defining custom reverse proxy routes via an `extra-proxies.js` file at the project root. The Gateway loads these configurations on startup. Additionally, the Nginx gateway manager no longer applies default prefix-stripping rewrites. Explicit `pathRewrite` rules are now required if prefix stripping is needed for any proxied service, including those defined externally. --- packages/server/extra-proxies.js | 7 ++ packages/server/gateway/index.js | 89 +++++++++++++++++++ .../server/gateway/managers/nginx/index.js | 13 +-- 3 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 packages/server/extra-proxies.js diff --git a/packages/server/extra-proxies.js b/packages/server/extra-proxies.js new file mode 100644 index 00000000..b484397e --- /dev/null +++ b/packages/server/extra-proxies.js @@ -0,0 +1,7 @@ +export default { + "/spectrum/*": { + target: process.env.SPECTRUM_API ?? "https://live.ragestudio.net", + pathRewrite: { "^/spectrum/(.*)": "/$1", "^/spectrum": "/" }, + websocket: true, + }, +} diff --git a/packages/server/gateway/index.js b/packages/server/gateway/index.js index fba8d70c..e6c30a52 100755 --- a/packages/server/gateway/index.js +++ b/packages/server/gateway/index.js @@ -165,6 +165,92 @@ export default class Gateway { } } + /** + * Loads and registers additional proxy routes from ../../extra-proxies.js + */ + async registerExtraProxies() { + try { + // Dynamic import is relative to the current file. + // extra-proxies.js can be CJS (module.exports = ...) or ESM (export default ...) + const extraProxiesModule = require( + path.resolve(process.cwd(), "extra-proxies.js"), + ) + const extraProxies = extraProxiesModule.default // Node's CJS/ESM interop puts module.exports on .default + + if ( + !extraProxies || + typeof extraProxies !== "object" || + Object.keys(extraProxies).length === 0 + ) { + console.log( + "[Gateway] No extra proxies defined in `extra-proxies.js`, file is empty, or format is invalid. Skipping.", + ) + return + } + + console.log( + `[Gateway] Registering extra proxies from 'extra-proxies.js'...`, + ) + + for (const proxyPathKey in extraProxies) { + if ( + Object.prototype.hasOwnProperty.call( + extraProxies, + proxyPathKey, + ) + ) { + const config = extraProxies[proxyPathKey] + if (!config || typeof config.target !== "string") { + console.warn( + `[Gateway] Skipping invalid extra proxy config for path: '${proxyPathKey}' in 'extra-proxies.js'. Target is missing or not a string.`, + ) + continue + } + + let registrationPath = proxyPathKey + + // Normalize paths ending with /* + // e.g., "/spectrum/*" becomes "/spectrum" + // e.g., "/*" becomes "/" + if (registrationPath.endsWith("/*")) { + registrationPath = registrationPath.slice(0, -2) + if (registrationPath === "") { + registrationPath = "/" + } + } + + console.log( + `[Gateway] Registering extra proxy: '${proxyPathKey}' (as '${registrationPath}') -> ${config.target}`, + ) + + await this.gateway.register({ + serviceId: `extra-proxy:${registrationPath}`, // Unique ID for this proxy rule + path: registrationPath, + target: config.target, + pathRewrite: config.pathRewrite, // undefined if not present + websocket: !!config.websocket, // false if not present or falsy + }) + } + } + } catch (error) { + // Handle cases where the extra-proxies.js file might not exist + if ( + error.code === "ERR_MODULE_NOT_FOUND" || + (error.message && + error.message.toLowerCase().includes("cannot find module")) + ) { + console.log( + "[Gateway] `extra-proxies.js` not found. Skipping extra proxy registration.", + ) + } else { + console.error( + "[Gateway] Error loading or registering extra proxies from `extra-proxies.js`:", + error, + ) + } + } + } + /** * Handle both router and websocket registration requests from services * @param {Service} service - Service registering a route or websocket @@ -304,6 +390,9 @@ export default class Gateway { await this.gateway.initialize() } + // Register any externally defined proxies before services start + await this.registerExtraProxies() + // Watch for service state changes Observable.observe(this.serviceRegistry, (changes) => { this.checkAllServicesReady() diff --git a/packages/server/gateway/managers/nginx/index.js b/packages/server/gateway/managers/nginx/index.js index ec9d6127..8511c16d 100755 --- a/packages/server/gateway/managers/nginx/index.js +++ b/packages/server/gateway/managers/nginx/index.js @@ -371,19 +371,12 @@ http { if (route.pathRewrite && Object.keys(route.pathRewrite).length > 0) { rewriteConfig += "# Path rewrite rules\n" + for (const [pattern, replacement] of Object.entries( route.pathRewrite, )) { // Improved rewrite pattern that preserves query parameters - rewriteConfig += `\trewrite ${pattern} ${replacement}$is_args$args break;` - } - } else { - // If no explicit rewrite is defined, but we need to strip the path prefix, - // Generate a default rewrite that preserves the URL structure - if (path !== "/") { - rewriteConfig += "# Default path rewrite to strip prefix\n" - rewriteConfig += `\trewrite ^${path}(/.*)$ $1$is_args$args break;\n` - rewriteConfig += `\trewrite ^${path}$ / break;` + rewriteConfig += `\nrewrite ${pattern} ${replacement} break;` } } @@ -423,6 +416,8 @@ ${locationDirective} { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; + ${rewriteConfig} + # Proxy pass to service proxy_pass ${route.target}; }