From f618d0b166c4b62ce1ca5072dd35315ebab9d740 Mon Sep 17 00:00:00 2001 From: Dawa Ometto Date: Thu, 21 Aug 2025 12:56:25 +0200 Subject: [PATCH 1/8] Optionally listen on unix sockets --- bin/configurable-http-proxy | 70 +++++++++++++++++++++---------- lib/testutil.js | 21 +++------- test/proxy_spec.js | 83 ++++++++++++++++++++++++++++++++++++- 3 files changed, 134 insertions(+), 40 deletions(-) diff --git a/bin/configurable-http-proxy b/bin/configurable-http-proxy index c85e53ee..d99e84ba 100755 --- a/bin/configurable-http-proxy +++ b/bin/configurable-http-proxy @@ -21,6 +21,7 @@ cli .version(pkg.version) .option("--ip ", "Public-facing IP of the proxy") .option("--port (defaults to 8000)", "Public-facing port of the proxy", parseInt) + .option("--socket ", "Path to a UNIX domain socket for the proxy to listen on. Alternative to specifying IP and port.") .option("--ssl-key ", "SSL key to use, if any") .option("--ssl-cert ", "SSL certificate to use, if any") .option("--ssl-ca ", "SSL certificate authority, if any") @@ -39,6 +40,7 @@ cli "Inward-facing port for API requests (defaults to --port=value+1)", parseInt ) + .option("--api-socket ", "Path to a UNIX domain socket for the API server to listen on. Alternative to specifying API IP and port.") .option("--api-ssl-key ", "SSL key to use, if any, for API requests") .option("--api-ssl-cert ", "SSL certificate to use, if any, for API requests") .option("--api-ssl-ca ", "SSL certificate authority, if any, for API requests") @@ -93,6 +95,7 @@ cli .option("--host-routing", "Use host routing (host as first level of path)") .option("--metrics-ip ", "IP for metrics server", "") .option("--metrics-port ", "Port of metrics server. Defaults to no metrics server") + .option("--metrics-socket ", "Path to a UNIX domain socket for the metrics server to listen on. Alternative to specifying metrics IP and port.") .option("--log-level ", "Log level (debug, info, warn, error)", "info") .option( "--timeout ", @@ -321,41 +324,62 @@ options.storageBackend = args.storageBackend; var proxy = new ConfigurableProxy(options); var listen = {}; -listen.port = parseInt(args.port) || 8000; -if (args.ip === "*") { - // handle ip=* alias for all interfaces + +if (args.socket) { + listen.proxyTarget = [args.socket]; log.warn( - "Interpreting ip='*' as all-interfaces. Preferred usage is 0.0.0.0 for all IPv4 or '' for all-interfaces." + "Proxy will listen on UNIX domain socket, --ip and --port options will be ignored." ); - args.ip = ""; +} else { + listen.port = parseInt(args.port) || 8000; + if (args.ip === "*") { + // handle ip=* alias for all interfaces + log.warn( + "Interpreting ip='*' as all-interfaces. Preferred usage is 0.0.0.0 for all IPv4 or '' for all-interfaces." + ); + args.ip = ""; + } + listen.ip = args.ip; + listen.proxyTarget = [listen.port, listen.ip]; } -listen.ip = args.ip; -listen.apiIp = args.apiIp; -listen.apiPort = args.apiPort || listen.port + 1; -listen.metricsIp = args.metricsIp; -listen.metricsPort = args.metricsPort; - -proxy.proxyServer.listen(listen.port, listen.ip); -proxy.apiServer.listen(listen.apiPort, listen.apiIp); -if (listen.metricsPort) { - proxy.metricsServer.listen(listen.metricsPort, listen.metricsIp); + +if (args.apiSocket) { + listen.apiSocket = [args.apiSocket]; + log.warn( + "API server will listen on UNIX domain socket, --api-ip and --api-port options will be ignored." + ); +} else { + listen.apiTarget = [args.apiPort || (listen.port ? listen.port + 1 : 8001), args.apiIp]; +} + +if (args.metricsSocket) { + listen.metricsSocket = [args.metricsSocket]; + log.warn( + "Metrics server will listen on UNIX domain socket, --metrics-ip and --metrics-port options will be ignored." + ); +} else { + listen.metricsTarget = [args.metricsPort, args.metricsIp]; +} + +proxy.proxyServer.listen(...listen.proxyTarget); +proxy.apiServer.listen(...listen.apiTarget); +if (listen.metricsTarget) { + proxy.metricsServer.listen(...listen.metricsTarget); } log.info( - "Proxying %s://%s:%s to %s", + "Proxying %s://%s to %s", options.ssl ? "https" : "http", - listen.ip || "*", - listen.port, + listen.proxyTarget.join(":"), options.defaultTarget || "(no default)" ); log.info( - "Proxy API at %s://%s:%s/api/routes", + "Proxy API at %s://%s/api/routes", options.apiSsl ? "https" : "http", - listen.apiIp || "*", - listen.apiPort + listen.apiTarget.join(":") ); if (listen.metricsPort) { - log.info("Serve metrics at %s://%s:%s/metrics", "http", listen.metricsIp, listen.metricsPort); + log.info("Serve metrics at %s://%s/metrics", "http", listen.metricsTarget.join(":")); } if (args.pidFile) { @@ -370,7 +394,7 @@ if (args.pidFile) { } // Redirect HTTP to HTTPS on the proxy's port -if (options.redirectPort && listen.port !== 80) { +if (options.redirectPort && listen.port && listen.port !== 80) { var http = require("http"); var redirectPort = options.redirectTo ? options.redirectTo : listen.port; var server = http diff --git a/lib/testutil.js b/lib/testutil.js index 088e6dbe..a06cc257 100644 --- a/lib/testutil.js +++ b/lib/testutil.js @@ -9,24 +9,12 @@ import { defaultLogger } from "./log.js"; var servers = []; // TODO: make this an options dict -export function addTarget(proxy, path, port, websocket, targetPath, sslOptions, unixSocketPath) { - var proto; - var listenTarget; - var target; - - if (unixSocketPath) { - listenTarget = decodeURIComponent(unixSocketPath); - proto = "http"; - target = "unix+" + proto + "://" + unixSocketPath; - } else { - proto = sslOptions ? "https" : "http"; - target = proto + "://" + "127.0.0.1:" + port; - listenTarget = port; - } +export function addTarget(proxy, path, port, websocket, targetPath, sslOptions) { + var proto = sslOptions ? "https" : "http"; + var target = proto + "://127.0.0.1:" + port; if (targetPath) { target = target + targetPath; } - var server; var data = { target: target, @@ -60,7 +48,7 @@ export function addTarget(proxy, path, port, websocket, targetPath, sslOptions, }); } - server.listen(listenTarget); + server.listen(port); servers.push(server); return proxy.addRoute(path, { target: target }).then(() => { // routes are created with an activity timestamp artificially shifted into the past @@ -166,6 +154,7 @@ export function teardownServers(callback) { }; for (var i = servers.length - 1; i >= 0; i--) { servers[i].close(onclose); + // closeAllConnections is implied in close in node >=19 // but this avoids waits between all tests with node 18 servers[i].closeAllConnections(); diff --git a/test/proxy_spec.js b/test/proxy_spec.js index 3246bd6a..fb52077a 100644 --- a/test/proxy_spec.js +++ b/test/proxy_spec.js @@ -515,7 +515,7 @@ describe("Proxy Tests", function () { it("proxy to unix socket test", function (done) { var proxyPort = 55557; - var unixSocketUri = "%2Ftmp%2Ftest.sock"; + var unixSocketUri = encodeURIComponent(tmp.tmpNameSync()); util .setupProxy(proxyPort, {}, []) @@ -530,3 +530,84 @@ describe("Proxy Tests", function () { .then(done); }); }); + +describe("Proxy Tests with Unix socket", function () { + var listenOptions = { + socket: tmp.tmpNameSync(), + }; + var requestOptions = new URL("http://localhost"); + requestOptions.socketPath = listenOptions.socket; + var proxy; + + beforeEach(function (callback) { + util.setupProxy(listenOptions).then(function (newProxy) { + proxy = newProxy; + callback(); + }); + }); + + afterEach(function (callback) { + util.teardownServers(callback); + }); + + it("unix socket HTTP request", function (done) { + http + .request(requestOptions, (res) => { + let recvData = []; + res.on("data", (chunk) => { + recvData.push(chunk); + }); + res.on("end", () => { + const body = JSON.parse(Buffer.concat(recvData).toString()); + expect(body).toEqual( + jasmine.objectContaining({ + path: "/", + }) + ); + }); + return proxy._routes.get("/").then((route) => { + expect(route.last_activity).toBeGreaterThan(proxy._setup_timestamp); + done(); + }); + }) + .on("error", (err) => { + expect("error").toEqual("ok"); + done(); + }) + .end(); + }); + + it("unix socket WebSocket request", function (done) { + var ws = new WebSocket("ws+unix:" + listenOptions.socket); + ws.on("error", function () { + // jasmine fail is only in master + expect("error").toEqual("ok"); + done(); + }); + var nmsgs = 0; + ws.on("message", function (msg) { + msg = msg.toString(); + if (nmsgs === 0) { + expect(msg).toEqual("connected"); + } else { + msg = JSON.parse(msg); + expect(msg).toEqual( + jasmine.objectContaining({ + path: "/", + message: "hi", + }) + ); + // check last_activity was updated + return proxy._routes.get("/").then((route) => { + expect(route.last_activity).toBeGreaterThan(proxy._setup_timestamp); + ws.close(); + done(); + }); + } + nmsgs++; + }); + ws.on("open", function () { + ws.send("hi"); + }); + }); +}); From 0f759c9d43585a4084686d9901e8fdb03de9cbae Mon Sep 17 00:00:00 2001 From: Dawa Ometto Date: Thu, 21 Aug 2025 17:42:03 +0200 Subject: [PATCH 2/8] Move listen option parsing to separate function --- bin/configurable-http-proxy | 44 +++++-------------------------------- lib/configproxy.js | 42 +++++++++++++++++++++++++++++++++++ lib/testutil.js | 38 ++++++++++++++++++++++---------- 3 files changed, 73 insertions(+), 51 deletions(-) diff --git a/bin/configurable-http-proxy b/bin/configurable-http-proxy index d99e84ba..6f5eca17 100755 --- a/bin/configurable-http-proxy +++ b/bin/configurable-http-proxy @@ -10,6 +10,8 @@ import fs from "node:fs"; import { Command } from "commander"; import ConfigurableProxy from "../lib/configproxy.js"; +import { parseListenOptions } from "../lib/configproxy.js"; + import { defaultLogger } from "../lib/log.js"; import { createRequire } from "node:module"; @@ -282,7 +284,7 @@ options.proxyTimeout = args.proxyTimeout; options.keepAliveTimeout = args.keepAliveTimeout; // metrics options -options.enableMetrics = !!args.metricsPort; +options.enableMetrics = !!args.metricsPort || !!args.metricsSocket; // certs need to be provided for https redirection if (!options.ssl && options.redirectPort) { @@ -323,43 +325,7 @@ options.storageBackend = args.storageBackend; var proxy = new ConfigurableProxy(options); -var listen = {}; - -if (args.socket) { - listen.proxyTarget = [args.socket]; - log.warn( - "Proxy will listen on UNIX domain socket, --ip and --port options will be ignored." - ); -} else { - listen.port = parseInt(args.port) || 8000; - if (args.ip === "*") { - // handle ip=* alias for all interfaces - log.warn( - "Interpreting ip='*' as all-interfaces. Preferred usage is 0.0.0.0 for all IPv4 or '' for all-interfaces." - ); - args.ip = ""; - } - listen.ip = args.ip; - listen.proxyTarget = [listen.port, listen.ip]; -} - -if (args.apiSocket) { - listen.apiSocket = [args.apiSocket]; - log.warn( - "API server will listen on UNIX domain socket, --api-ip and --api-port options will be ignored." - ); -} else { - listen.apiTarget = [args.apiPort || (listen.port ? listen.port + 1 : 8001), args.apiIp]; -} - -if (args.metricsSocket) { - listen.metricsSocket = [args.metricsSocket]; - log.warn( - "Metrics server will listen on UNIX domain socket, --metrics-ip and --metrics-port options will be ignored." - ); -} else { - listen.metricsTarget = [args.metricsPort, args.metricsIp]; -} +var listen = parseListenOptions(args); proxy.proxyServer.listen(...listen.proxyTarget); proxy.apiServer.listen(...listen.apiTarget); @@ -378,7 +344,7 @@ log.info( options.apiSsl ? "https" : "http", listen.apiTarget.join(":") ); -if (listen.metricsPort) { +if (listen.metricsTarget) { log.info("Serve metrics at %s://%s/metrics", "http", listen.metricsTarget.join(":")); } diff --git a/lib/configproxy.js b/lib/configproxy.js index 98663786..15eb8177 100644 --- a/lib/configproxy.js +++ b/lib/configproxy.js @@ -24,6 +24,48 @@ const require = createRequire(import.meta.url); const __dirname = path.dirname(fileURLToPath(import.meta.url)); +export function parseListenOptions(args) { + var listen = {}; + + if (args.socket) { + listen.proxyTarget = [args.socket]; + log.warn( + "Proxy will listen on UNIX domain socket, --ip and --port options will be ignored." + ); + } else { + listen.port = parseInt(args.port) || 8000; + if (args.ip === "*") { + // handle ip=* alias for all interfaces + log.warn( + "Interpreting ip='*' as all-interfaces. Preferred usage is 0.0.0.0 for all IPv4 or '' for all-interfaces." + ); + args.ip = ""; + } + listen.ip = args.ip; + listen.proxyTarget = [listen.port, listen.ip]; + } + + if (args.apiSocket) { + listen.apiSocket = [args.apiSocket]; + log.warn( + "API server will listen on UNIX domain socket, --api-ip and --api-port options will be ignored." + ); + } else { + listen.apiPort = args.apiPort ? parseInt(args.apiPort) : (listen.port ? listen.port + 1 : 8001); + listen.apiTarget = [listen.apiPort, args.apiIp]; + } + + if (args.metricsSocket) { + listen.metricsSocket = [args.metricsSocket]; + log.warn( + "Metrics server will listen on UNIX domain socket, --metrics-ip and --metrics-port options will be ignored." + ); + } else if (args.metricsPort) { + listen.metricsTarget = [parseInt(args.metricsPort), args.metricsIp]; + } + return listen; +} + function bound(that, method) { // bind a method, to ensure `this=that` when it is called // because prototype languages are bad diff --git a/lib/testutil.js b/lib/testutil.js index a06cc257..0d916eb7 100644 --- a/lib/testutil.js +++ b/lib/testutil.js @@ -1,17 +1,30 @@ "use strict"; +import fs from "node:fs"; import http from "node:http"; import https from "node:https"; import { WebSocketServer } from "ws"; -import { ConfigurableProxy } from "./configproxy.js"; +import { ConfigurableProxy, parseListenOptions } from "./configproxy.js"; import { defaultLogger } from "./log.js"; var servers = []; // TODO: make this an options dict -export function addTarget(proxy, path, port, websocket, targetPath, sslOptions) { - var proto = sslOptions ? "https" : "http"; - var target = proto + "://127.0.0.1:" + port; +export function addTarget(proxy, path, port, websocket, targetPath, sslOptions, unixSocketPath) { + var proto; + var listenTarget; + var target; + + if (unixSocketPath) { + listenTarget = decodeURIComponent(unixSocketPath); + proto = "http"; + target = "unix+" + proto + "://" + unixSocketPath; + } else { + proto = sslOptions ? "https" : "http"; + target = proto + "://" + "127.0.0.1:" + port; + listenTarget = port; + } + if (targetPath) { target = target + targetPath; } @@ -48,7 +61,7 @@ export function addTarget(proxy, path, port, websocket, targetPath, sslOptions) }); } - server.listen(port); + server.listen(listenTarget); servers.push(server); return proxy.addRoute(path, { target: target }).then(() => { // routes are created with an activity timestamp artificially shifted into the past @@ -87,14 +100,16 @@ export function addTargets(proxy, paths, port) { }); } -export function setupProxy(port, options, paths) { +export function setupProxy(listenOptions, options, paths) { options = options || {}; options.authToken = "secret"; options.log = defaultLogger({ level: "error" }); - + var listen = parseListenOptions(listenOptions, options.log); + var port = listen.port || 8000; + var ip = listen.ip; var proxy = new ConfigurableProxy(options); proxy._setup_timestamp = new Date(new Date().getTime() - 60000); - var ip = "127.0.0.1"; + var countdown = 2; var resolvePromise; @@ -134,10 +149,10 @@ export function setupProxy(port, options, paths) { proxy.proxyServer.on("listening", onlisten); addTargets(proxy, paths || ["/"], port + 2).then(function () { - proxy.proxyServer.listen(port, ip); - proxy.apiServer.listen(port + 1, ip); + proxy.proxyServer.listen(...listen.proxyTarget); + proxy.apiServer.listen(...listen.apiTarget); if (options.enableMetrics) { - proxy.metricsServer.listen(port + 3, ip); + proxy.metricsServer.listen(...listen.metricsTarget); } }); return p; @@ -154,7 +169,6 @@ export function teardownServers(callback) { }; for (var i = servers.length - 1; i >= 0; i--) { servers[i].close(onclose); - // closeAllConnections is implied in close in node >=19 // but this avoids waits between all tests with node 18 servers[i].closeAllConnections(); From fbcadf7b182a8c11fb2455efcaf42b59d7f96e98 Mon Sep 17 00:00:00 2001 From: Dawa Ometto Date: Tue, 26 Aug 2025 16:15:00 +0200 Subject: [PATCH 3/8] Refactor tests: let setupProxy take unix socket as well as TCP socket options. --- test/api_spec.js | 10 ++++++--- test/proxy_spec.js | 52 +++++++++++++++++++++++++++++----------------- 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/test/api_spec.js b/test/api_spec.js index 0511b55b..0affcd82 100644 --- a/test/api_spec.js +++ b/test/api_spec.js @@ -8,15 +8,19 @@ log.remove(log.transports.Console); describe("API Tests", function () { var port = 8902; - var apiPort = port + 1; + var listenOptions = { + port: port, + apiPort: 8903, + ip: '127.0.0.1' + }; var proxy; - var apiUrl = "http://127.0.0.1:" + apiPort + "/api/routes"; + var apiUrl = "http://" + listenOptions.ip + ":" + listenOptions.apiPort + "/api/routes"; var r; beforeEach(function (callback) { util - .setupProxy(port) + .setupProxy(listenOptions) .then(function (newProxy) { proxy = newProxy; }) diff --git a/test/proxy_spec.js b/test/proxy_spec.js index fb52077a..9e16f7b7 100644 --- a/test/proxy_spec.js +++ b/test/proxy_spec.js @@ -13,14 +13,18 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); describe("Proxy Tests", function () { var port = 8902; + var listenOptions = { + port: port, + ip: '127.0.0.1' + }; var testPort = port + 10; var proxy; - var proxyUrl = "http://127.0.0.1:" + port; + var proxyUrl = "http://" + listenOptions.ip + ":" + port; var hostTest = "test.localhost.jovyan.org"; var hostUrl = "http://" + hostTest + ":" + port; beforeEach(function (callback) { - util.setupProxy(port).then(function (newProxy) { + util.setupProxy(listenOptions).then(function (newProxy) { proxy = newProxy; callback(); }); @@ -338,10 +342,12 @@ describe("Proxy Tests", function () { }); it("custom error target", function (done) { - var proxyPort = 55550; + var listenOptions = { + port: 55550 + }; util - .setupProxy(proxyPort, { errorTarget: "http://127.0.0.1:55565" }, []) - .then(() => fetch("http://127.0.0.1:" + proxyPort + "/foo/bar")) + .setupProxy(listenOptions, { errorTarget: "http://127.0.0.1:55565" }, []) + .then(() => fetch("http://127.0.0.1:" + listenOptions.port + "/foo/bar")) .then((res) => { expect(res.status).toEqual(404); expect(res.headers.get("content-type")).toEqual("text/plain"); @@ -405,10 +411,12 @@ describe("Proxy Tests", function () { }); it("backend error", function (done) { - var proxyPort = 55550; + var listenOptions = { + port: 55550 + }; util - .setupProxy(proxyPort, { errorTarget: "http://127.0.0.1:55565" }, []) - .then(() => fetch("http://127.0.0.1:" + proxyPort + "/%")) + .setupProxy(listenOptions, { errorTarget: "http://127.0.0.1:55565" }, []) + .then(() => fetch("http://127.0.0.1:" + listenOptions.port + "/%")) .then((res) => { expect(res.status).toEqual(500); expect(res.headers.get("content-type")).toEqual("text/plain"); @@ -433,7 +441,9 @@ describe("Proxy Tests", function () { }); it("Redirect location with rewriting", function (done) { - var proxyPort = 55556; + var listenOptions = { + port: 55556 + }; var options = { protocolRewrite: "https", autoRewrite: true, @@ -442,10 +452,10 @@ describe("Proxy Tests", function () { // where the backend server redirects us. // Note that http-proxy requires (logically) the redirection to be to the same (internal) host. var redirectTo = "https://127.0.0.1:" + testPort + "/whatever"; - var expectedRedirect = "https://127.0.0.1:" + proxyPort + "/whatever"; + var expectedRedirect = "https://127.0.0.1:" + listenOptions.port + "/whatever"; util - .setupProxy(proxyPort, options, []) + .setupProxy(listenOptions, options, []) .then((proxy) => util.addTargetRedirecting( proxy, @@ -456,7 +466,7 @@ describe("Proxy Tests", function () { ) ) .then(() => - fetch("http://127.0.0.1:" + proxyPort + "/external/urlpath/", { redirect: "manual" }) + fetch("http://127.0.0.1:" + listenOptions.port + "/external/urlpath/", { redirect: "manual" }) ) .then((res) => { expect(res.status).toEqual(301); @@ -483,8 +493,10 @@ describe("Proxy Tests", function () { done(); return; } - var proxyPort = 55556; - var testPort = proxyPort + 20; + var listenOptions = { + port: 55556 + }; + var testPort = listenOptions.port + 20; var options = { clientSsl: { key: fs.readFileSync(path.resolve(__dirname, "ssl/proxy-client/proxy-client.key")), @@ -494,7 +506,7 @@ describe("Proxy Tests", function () { }; util - .setupProxy(proxyPort, options, []) + .setupProxy(listenOptions, options, []) .then((proxy) => util.addTarget(proxy, "/backend/", testPort, false, null, { key: fs.readFileSync(path.resolve(__dirname, "ssl/backend/backend.key")), @@ -503,7 +515,7 @@ describe("Proxy Tests", function () { requestCert: true, }) ) - .then(() => fetch("http://127.0.0.1:" + proxyPort + "/backend/urlpath/")) + .then(() => fetch("http://127.0.0.1:" + listenOptions.port + "/backend/urlpath/")) .then((res) => { expect(res.status).toEqual(200); }) @@ -514,13 +526,15 @@ describe("Proxy Tests", function () { }); it("proxy to unix socket test", function (done) { - var proxyPort = 55557; + var listenOptions = { + port: 55557, + }; var unixSocketUri = encodeURIComponent(tmp.tmpNameSync()); util - .setupProxy(proxyPort, {}, []) + .setupProxy(listenOptions, {}, []) .then((proxy) => util.addTarget(proxy, "/unix", 0, false, null, null, unixSocketUri)) - .then(() => fetch("http://127.0.0.1:" + proxyPort + "/unix")) + .then(() => fetch("http://127.0.0.1:" + listenOptions.port + "/unix")) .then((res) => { expect(res.status).toEqual(200); }) From ff2fa60c611ce19aaa274d941aebdab97bd4f62e Mon Sep 17 00:00:00 2001 From: Dawa Ometto Date: Tue, 26 Aug 2025 18:41:38 +0200 Subject: [PATCH 4/8] Add tests for proxy with unix socket --- bin/configurable-http-proxy | 2 +- lib/configproxy.js | 16 ++++++++-------- package-lock.json | 11 +++++++++++ package.json | 3 ++- test/api_spec.js | 2 +- test/proxy_spec.js | 16 ++++++++++------ 6 files changed, 33 insertions(+), 17 deletions(-) diff --git a/bin/configurable-http-proxy b/bin/configurable-http-proxy index 6f5eca17..7fb926d7 100755 --- a/bin/configurable-http-proxy +++ b/bin/configurable-http-proxy @@ -325,7 +325,7 @@ options.storageBackend = args.storageBackend; var proxy = new ConfigurableProxy(options); -var listen = parseListenOptions(args); +var listen = parseListenOptions(args, log); proxy.proxyServer.listen(...listen.proxyTarget); proxy.apiServer.listen(...listen.apiTarget); diff --git a/lib/configproxy.js b/lib/configproxy.js index 15eb8177..2f997e18 100644 --- a/lib/configproxy.js +++ b/lib/configproxy.js @@ -24,19 +24,19 @@ const require = createRequire(import.meta.url); const __dirname = path.dirname(fileURLToPath(import.meta.url)); -export function parseListenOptions(args) { +export function parseListenOptions(args, logger) { var listen = {}; if (args.socket) { listen.proxyTarget = [args.socket]; - log.warn( + logger.warn( "Proxy will listen on UNIX domain socket, --ip and --port options will be ignored." ); } else { listen.port = parseInt(args.port) || 8000; if (args.ip === "*") { // handle ip=* alias for all interfaces - log.warn( + logger.warn( "Interpreting ip='*' as all-interfaces. Preferred usage is 0.0.0.0 for all IPv4 or '' for all-interfaces." ); args.ip = ""; @@ -44,20 +44,20 @@ export function parseListenOptions(args) { listen.ip = args.ip; listen.proxyTarget = [listen.port, listen.ip]; } - + if (args.apiSocket) { listen.apiSocket = [args.apiSocket]; - log.warn( + logger.warn( "API server will listen on UNIX domain socket, --api-ip and --api-port options will be ignored." ); } else { - listen.apiPort = args.apiPort ? parseInt(args.apiPort) : (listen.port ? listen.port + 1 : 8001); + listen.apiPort = args.apiPort ? parseInt(args.apiPort) : listen.port ? listen.port + 1 : 8001; listen.apiTarget = [listen.apiPort, args.apiIp]; } - + if (args.metricsSocket) { listen.metricsSocket = [args.metricsSocket]; - log.warn( + logger.warn( "Metrics server will listen on UNIX domain socket, --metrics-ip and --metrics-port options will be ignored." ); } else if (args.metricsPort) { diff --git a/package-lock.json b/package-lock.json index 4c2466b3..8523ff9f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "c8": "^10.1.3", "jasmine": "^5.6.0", "node-fetch": "^2.7.0", + "tmp": "^0.2", "ws": "^8.4.0" }, "engines": { @@ -1248,6 +1249,16 @@ "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "license": "MIT" }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", diff --git a/package.json b/package.json index 86a84dbf..c4f43ed3 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "c8": "^10.1.3", "jasmine": "^5.6.0", "node-fetch": "^2.7.0", - "ws": "^8.4.0" + "ws": "^8.4.0", + "tmp": "^0.2" }, "engines": { "node": ">= 18" diff --git a/test/api_spec.js b/test/api_spec.js index 0affcd82..a909c7a3 100644 --- a/test/api_spec.js +++ b/test/api_spec.js @@ -11,7 +11,7 @@ describe("API Tests", function () { var listenOptions = { port: port, apiPort: 8903, - ip: '127.0.0.1' + ip: "127.0.0.1", }; var proxy; var apiUrl = "http://" + listenOptions.ip + ":" + listenOptions.apiPort + "/api/routes"; diff --git a/test/proxy_spec.js b/test/proxy_spec.js index 9e16f7b7..6ad084a4 100644 --- a/test/proxy_spec.js +++ b/test/proxy_spec.js @@ -1,11 +1,13 @@ import fs from "node:fs"; import path from "node:path"; +import tmp from "tmp" import { fileURLToPath } from "node:url"; import fetch from "node-fetch"; import WebSocket from "ws"; import { ConfigurableProxy } from "../lib/configproxy.js"; import * as util from "../lib/testutil.js"; +import http from "node:http"; jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; @@ -15,7 +17,7 @@ describe("Proxy Tests", function () { var port = 8902; var listenOptions = { port: port, - ip: '127.0.0.1' + ip: "127.0.0.1", }; var testPort = port + 10; var proxy; @@ -343,7 +345,7 @@ describe("Proxy Tests", function () { it("custom error target", function (done) { var listenOptions = { - port: 55550 + port: 55550, }; util .setupProxy(listenOptions, { errorTarget: "http://127.0.0.1:55565" }, []) @@ -412,7 +414,7 @@ describe("Proxy Tests", function () { it("backend error", function (done) { var listenOptions = { - port: 55550 + port: 55550, }; util .setupProxy(listenOptions, { errorTarget: "http://127.0.0.1:55565" }, []) @@ -442,7 +444,7 @@ describe("Proxy Tests", function () { it("Redirect location with rewriting", function (done) { var listenOptions = { - port: 55556 + port: 55556, }; var options = { protocolRewrite: "https", @@ -466,7 +468,9 @@ describe("Proxy Tests", function () { ) ) .then(() => - fetch("http://127.0.0.1:" + listenOptions.port + "/external/urlpath/", { redirect: "manual" }) + fetch("http://127.0.0.1:" + listenOptions.port + "/external/urlpath/", { + redirect: "manual", + }) ) .then((res) => { expect(res.status).toEqual(301); @@ -494,7 +498,7 @@ describe("Proxy Tests", function () { return; } var listenOptions = { - port: 55556 + port: 55556, }; var testPort = listenOptions.port + 20; var options = { From 38a2551ae4651f8cf943a979b979c6e0591a9b24 Mon Sep 17 00:00:00 2001 From: Dawa Ometto Date: Mon, 8 Dec 2025 16:44:13 +0100 Subject: [PATCH 5/8] Express conflicts between socket and ip, port options via commander API --- bin/configurable-http-proxy | 8 ++++---- lib/configproxy.js | 9 --------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/bin/configurable-http-proxy b/bin/configurable-http-proxy index 7fb926d7..847b7772 100755 --- a/bin/configurable-http-proxy +++ b/bin/configurable-http-proxy @@ -7,7 +7,7 @@ // import fs from "node:fs"; -import { Command } from "commander"; +import { Command, Option } from "commander"; import ConfigurableProxy from "../lib/configproxy.js"; import { parseListenOptions } from "../lib/configproxy.js"; @@ -23,7 +23,7 @@ cli .version(pkg.version) .option("--ip ", "Public-facing IP of the proxy") .option("--port (defaults to 8000)", "Public-facing port of the proxy", parseInt) - .option("--socket ", "Path to a UNIX domain socket for the proxy to listen on. Alternative to specifying IP and port.") + .addOption(new Option("--socket ", "Path to a UNIX domain socket for the proxy to listen on. Alternative to specifying IP and port.").conflicts(['port', 'ip'])) .option("--ssl-key ", "SSL key to use, if any") .option("--ssl-cert ", "SSL certificate to use, if any") .option("--ssl-ca ", "SSL certificate authority, if any") @@ -42,7 +42,7 @@ cli "Inward-facing port for API requests (defaults to --port=value+1)", parseInt ) - .option("--api-socket ", "Path to a UNIX domain socket for the API server to listen on. Alternative to specifying API IP and port.") + .addOption(new Option("--api-socket ", "Path to a UNIX domain socket for the API server to listen on. Alternative to specifying API IP and port.").conflicts(['api-port', 'api-ip'])) .option("--api-ssl-key ", "SSL key to use, if any, for API requests") .option("--api-ssl-cert ", "SSL certificate to use, if any, for API requests") .option("--api-ssl-ca ", "SSL certificate authority, if any, for API requests") @@ -97,7 +97,7 @@ cli .option("--host-routing", "Use host routing (host as first level of path)") .option("--metrics-ip ", "IP for metrics server", "") .option("--metrics-port ", "Port of metrics server. Defaults to no metrics server") - .option("--metrics-socket ", "Path to a UNIX domain socket for the metrics server to listen on. Alternative to specifying metrics IP and port.") + .addOption(new Option("--metrics-socket ", "Path to a UNIX domain socket for the metrics server to listen on. Alternative to specifying metrics IP and port.").conflicts(['metrics-port', 'metrics-ip'])) .option("--log-level ", "Log level (debug, info, warn, error)", "info") .option( "--timeout ", diff --git a/lib/configproxy.js b/lib/configproxy.js index 2f997e18..8a4962b8 100644 --- a/lib/configproxy.js +++ b/lib/configproxy.js @@ -29,9 +29,6 @@ export function parseListenOptions(args, logger) { if (args.socket) { listen.proxyTarget = [args.socket]; - logger.warn( - "Proxy will listen on UNIX domain socket, --ip and --port options will be ignored." - ); } else { listen.port = parseInt(args.port) || 8000; if (args.ip === "*") { @@ -47,9 +44,6 @@ export function parseListenOptions(args, logger) { if (args.apiSocket) { listen.apiSocket = [args.apiSocket]; - logger.warn( - "API server will listen on UNIX domain socket, --api-ip and --api-port options will be ignored." - ); } else { listen.apiPort = args.apiPort ? parseInt(args.apiPort) : listen.port ? listen.port + 1 : 8001; listen.apiTarget = [listen.apiPort, args.apiIp]; @@ -57,9 +51,6 @@ export function parseListenOptions(args, logger) { if (args.metricsSocket) { listen.metricsSocket = [args.metricsSocket]; - logger.warn( - "Metrics server will listen on UNIX domain socket, --metrics-ip and --metrics-port options will be ignored." - ); } else if (args.metricsPort) { listen.metricsTarget = [parseInt(args.metricsPort), args.metricsIp]; } From 43bfcbd76c27041142ca382fbe93487aaaa82604 Mon Sep 17 00:00:00 2001 From: Dawa Ometto Date: Mon, 8 Dec 2025 16:46:45 +0100 Subject: [PATCH 6/8] Add new socket CLI options to readme --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index ba05a7bf..d51acd6f 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,7 @@ Options: -V, --version output the version number --ip Public-facing IP of the proxy --port (defaults to 8000) Public-facing port of the proxy + --socket Path to a UNIX domain socket for the proxy to listen on. Alternative to specifying IP and port. --ssl-key SSL key to use, if any --ssl-cert SSL certificate to use, if any --ssl-ca SSL certificate authority, if any @@ -122,6 +123,7 @@ Options: --ssl-dhparam SSL Diffie-Helman Parameters pem file, if any --api-ip Inward-facing IP for API requests (default: "localhost") --api-port Inward-facing port for API requests (defaults to --port=value+1) + --api-socket Path to a UNIX domain socket for the API to listen on. Alternative to specifying API IP and port. --api-ssl-key SSL key to use, if any, for API requests --api-ssl-cert SSL certificate to use, if any, for API requests --api-ssl-ca SSL certificate authority, if any, for API requests @@ -149,6 +151,7 @@ Options: --host-routing Use host routing (host as first level of path) --metrics-ip IP for metrics server (default: "") --metrics-port Port of metrics server. Defaults to no metrics server + --metrics-socket Path to a UNIX domain socket for the metrics server to listen on. Alternative to specifying metrics IP and port. --log-level Log level (debug, info, warn, error) (default: "info") --timeout Timeout (in millis) when proxy drops connection for a request. --proxy-timeout Timeout (in millis) when proxy receives no response from target. From 644637a3bcdc227d86916749de56640a4a96ab02 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 19:42:13 +0000 Subject: [PATCH 7/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- test/proxy_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/proxy_spec.js b/test/proxy_spec.js index 6ad084a4..dc2083be 100644 --- a/test/proxy_spec.js +++ b/test/proxy_spec.js @@ -1,6 +1,6 @@ import fs from "node:fs"; import path from "node:path"; -import tmp from "tmp" +import tmp from "tmp"; import { fileURLToPath } from "node:url"; import fetch from "node-fetch"; import WebSocket from "ws"; From 473ac9359db7c3f87ec1f09c06d73b59b7213c6d Mon Sep 17 00:00:00 2001 From: Dawa Ometto Date: Mon, 8 Dec 2025 20:44:10 +0100 Subject: [PATCH 8/8] Fix linter --- lib/testutil.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/testutil.js b/lib/testutil.js index 0d916eb7..15bac6cd 100644 --- a/lib/testutil.js +++ b/lib/testutil.js @@ -1,6 +1,5 @@ "use strict"; -import fs from "node:fs"; import http from "node:http"; import https from "node:https"; import { WebSocketServer } from "ws";