Skip to content

Commit f618d0b

Browse files
committed
Optionally listen on unix sockets
1 parent 198a770 commit f618d0b

File tree

3 files changed

+134
-40
lines changed

3 files changed

+134
-40
lines changed

bin/configurable-http-proxy

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ cli
2121
.version(pkg.version)
2222
.option("--ip <ip-address>", "Public-facing IP of the proxy")
2323
.option("--port <n> (defaults to 8000)", "Public-facing port of the proxy", parseInt)
24+
.option("--socket <path>", "Path to a UNIX domain socket for the proxy to listen on. Alternative to specifying IP and port.")
2425
.option("--ssl-key <keyfile>", "SSL key to use, if any")
2526
.option("--ssl-cert <certfile>", "SSL certificate to use, if any")
2627
.option("--ssl-ca <ca-file>", "SSL certificate authority, if any")
@@ -39,6 +40,7 @@ cli
3940
"Inward-facing port for API requests (defaults to --port=value+1)",
4041
parseInt
4142
)
43+
.option("--api-socket <path>", "Path to a UNIX domain socket for the API server to listen on. Alternative to specifying API IP and port.")
4244
.option("--api-ssl-key <keyfile>", "SSL key to use, if any, for API requests")
4345
.option("--api-ssl-cert <certfile>", "SSL certificate to use, if any, for API requests")
4446
.option("--api-ssl-ca <ca-file>", "SSL certificate authority, if any, for API requests")
@@ -93,6 +95,7 @@ cli
9395
.option("--host-routing", "Use host routing (host as first level of path)")
9496
.option("--metrics-ip <ip>", "IP for metrics server", "")
9597
.option("--metrics-port <n>", "Port of metrics server. Defaults to no metrics server")
98+
.option("--metrics-socket <path>", "Path to a UNIX domain socket for the metrics server to listen on. Alternative to specifying metrics IP and port.")
9699
.option("--log-level <loglevel>", "Log level (debug, info, warn, error)", "info")
97100
.option(
98101
"--timeout <n>",
@@ -321,41 +324,62 @@ options.storageBackend = args.storageBackend;
321324
var proxy = new ConfigurableProxy(options);
322325

323326
var listen = {};
324-
listen.port = parseInt(args.port) || 8000;
325-
if (args.ip === "*") {
326-
// handle ip=* alias for all interfaces
327+
328+
if (args.socket) {
329+
listen.proxyTarget = [args.socket];
327330
log.warn(
328-
"Interpreting ip='*' as all-interfaces. Preferred usage is 0.0.0.0 for all IPv4 or '' for all-interfaces."
331+
"Proxy will listen on UNIX domain socket, --ip and --port options will be ignored."
329332
);
330-
args.ip = "";
333+
} else {
334+
listen.port = parseInt(args.port) || 8000;
335+
if (args.ip === "*") {
336+
// handle ip=* alias for all interfaces
337+
log.warn(
338+
"Interpreting ip='*' as all-interfaces. Preferred usage is 0.0.0.0 for all IPv4 or '' for all-interfaces."
339+
);
340+
args.ip = "";
341+
}
342+
listen.ip = args.ip;
343+
listen.proxyTarget = [listen.port, listen.ip];
331344
}
332-
listen.ip = args.ip;
333-
listen.apiIp = args.apiIp;
334-
listen.apiPort = args.apiPort || listen.port + 1;
335-
listen.metricsIp = args.metricsIp;
336-
listen.metricsPort = args.metricsPort;
337-
338-
proxy.proxyServer.listen(listen.port, listen.ip);
339-
proxy.apiServer.listen(listen.apiPort, listen.apiIp);
340-
if (listen.metricsPort) {
341-
proxy.metricsServer.listen(listen.metricsPort, listen.metricsIp);
345+
346+
if (args.apiSocket) {
347+
listen.apiSocket = [args.apiSocket];
348+
log.warn(
349+
"API server will listen on UNIX domain socket, --api-ip and --api-port options will be ignored."
350+
);
351+
} else {
352+
listen.apiTarget = [args.apiPort || (listen.port ? listen.port + 1 : 8001), args.apiIp];
353+
}
354+
355+
if (args.metricsSocket) {
356+
listen.metricsSocket = [args.metricsSocket];
357+
log.warn(
358+
"Metrics server will listen on UNIX domain socket, --metrics-ip and --metrics-port options will be ignored."
359+
);
360+
} else {
361+
listen.metricsTarget = [args.metricsPort, args.metricsIp];
362+
}
363+
364+
proxy.proxyServer.listen(...listen.proxyTarget);
365+
proxy.apiServer.listen(...listen.apiTarget);
366+
if (listen.metricsTarget) {
367+
proxy.metricsServer.listen(...listen.metricsTarget);
342368
}
343369

344370
log.info(
345-
"Proxying %s://%s:%s to %s",
371+
"Proxying %s://%s to %s",
346372
options.ssl ? "https" : "http",
347-
listen.ip || "*",
348-
listen.port,
373+
listen.proxyTarget.join(":"),
349374
options.defaultTarget || "(no default)"
350375
);
351376
log.info(
352-
"Proxy API at %s://%s:%s/api/routes",
377+
"Proxy API at %s://%s/api/routes",
353378
options.apiSsl ? "https" : "http",
354-
listen.apiIp || "*",
355-
listen.apiPort
379+
listen.apiTarget.join(":")
356380
);
357381
if (listen.metricsPort) {
358-
log.info("Serve metrics at %s://%s:%s/metrics", "http", listen.metricsIp, listen.metricsPort);
382+
log.info("Serve metrics at %s://%s/metrics", "http", listen.metricsTarget.join(":"));
359383
}
360384

361385
if (args.pidFile) {
@@ -370,7 +394,7 @@ if (args.pidFile) {
370394
}
371395

372396
// Redirect HTTP to HTTPS on the proxy's port
373-
if (options.redirectPort && listen.port !== 80) {
397+
if (options.redirectPort && listen.port && listen.port !== 80) {
374398
var http = require("http");
375399
var redirectPort = options.redirectTo ? options.redirectTo : listen.port;
376400
var server = http

lib/testutil.js

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,12 @@ import { defaultLogger } from "./log.js";
99
var servers = [];
1010

1111
// TODO: make this an options dict
12-
export function addTarget(proxy, path, port, websocket, targetPath, sslOptions, unixSocketPath) {
13-
var proto;
14-
var listenTarget;
15-
var target;
16-
17-
if (unixSocketPath) {
18-
listenTarget = decodeURIComponent(unixSocketPath);
19-
proto = "http";
20-
target = "unix+" + proto + "://" + unixSocketPath;
21-
} else {
22-
proto = sslOptions ? "https" : "http";
23-
target = proto + "://" + "127.0.0.1:" + port;
24-
listenTarget = port;
25-
}
12+
export function addTarget(proxy, path, port, websocket, targetPath, sslOptions) {
13+
var proto = sslOptions ? "https" : "http";
14+
var target = proto + "://127.0.0.1:" + port;
2615
if (targetPath) {
2716
target = target + targetPath;
2817
}
29-
3018
var server;
3119
var data = {
3220
target: target,
@@ -60,7 +48,7 @@ export function addTarget(proxy, path, port, websocket, targetPath, sslOptions,
6048
});
6149
}
6250

63-
server.listen(listenTarget);
51+
server.listen(port);
6452
servers.push(server);
6553
return proxy.addRoute(path, { target: target }).then(() => {
6654
// routes are created with an activity timestamp artificially shifted into the past
@@ -166,6 +154,7 @@ export function teardownServers(callback) {
166154
};
167155
for (var i = servers.length - 1; i >= 0; i--) {
168156
servers[i].close(onclose);
157+
169158
// closeAllConnections is implied in close in node >=19
170159
// but this avoids waits between all tests with node 18
171160
servers[i].closeAllConnections();

test/proxy_spec.js

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ describe("Proxy Tests", function () {
515515

516516
it("proxy to unix socket test", function (done) {
517517
var proxyPort = 55557;
518-
var unixSocketUri = "%2Ftmp%2Ftest.sock";
518+
var unixSocketUri = encodeURIComponent(tmp.tmpNameSync());
519519

520520
util
521521
.setupProxy(proxyPort, {}, [])
@@ -530,3 +530,84 @@ describe("Proxy Tests", function () {
530530
.then(done);
531531
});
532532
});
533+
534+
describe("Proxy Tests with Unix socket", function () {
535+
var listenOptions = {
536+
socket: tmp.tmpNameSync(),
537+
};
538+
var requestOptions = new URL("http://localhost");
539+
requestOptions.socketPath = listenOptions.socket;
540+
var proxy;
541+
542+
beforeEach(function (callback) {
543+
util.setupProxy(listenOptions).then(function (newProxy) {
544+
proxy = newProxy;
545+
callback();
546+
});
547+
});
548+
549+
afterEach(function (callback) {
550+
util.teardownServers(callback);
551+
});
552+
553+
it("unix socket HTTP request", function (done) {
554+
http
555+
.request(requestOptions, (res) => {
556+
let recvData = [];
557+
res.on("data", (chunk) => {
558+
recvData.push(chunk);
559+
});
560+
res.on("end", () => {
561+
const body = JSON.parse(Buffer.concat(recvData).toString());
562+
expect(body).toEqual(
563+
jasmine.objectContaining({
564+
path: "/",
565+
})
566+
);
567+
});
568+
return proxy._routes.get("/").then((route) => {
569+
expect(route.last_activity).toBeGreaterThan(proxy._setup_timestamp);
570+
done();
571+
});
572+
})
573+
.on("error", (err) => {
574+
expect("error").toEqual("ok");
575+
done();
576+
})
577+
.end();
578+
});
579+
580+
it("unix socket WebSocket request", function (done) {
581+
var ws = new WebSocket("ws+unix:" + listenOptions.socket);
582+
ws.on("error", function () {
583+
// jasmine fail is only in master
584+
expect("error").toEqual("ok");
585+
done();
586+
});
587+
var nmsgs = 0;
588+
ws.on("message", function (msg) {
589+
msg = msg.toString();
590+
if (nmsgs === 0) {
591+
expect(msg).toEqual("connected");
592+
} else {
593+
msg = JSON.parse(msg);
594+
expect(msg).toEqual(
595+
jasmine.objectContaining({
596+
path: "/",
597+
message: "hi",
598+
})
599+
);
600+
// check last_activity was updated
601+
return proxy._routes.get("/").then((route) => {
602+
expect(route.last_activity).toBeGreaterThan(proxy._setup_timestamp);
603+
ws.close();
604+
done();
605+
});
606+
}
607+
nmsgs++;
608+
});
609+
ws.on("open", function () {
610+
ws.send("hi");
611+
});
612+
});
613+
});

0 commit comments

Comments
 (0)