Skip to content

Commit c2a6ac0

Browse files
feat: add additional endpoints option
1 parent f4756b0 commit c2a6ac0

File tree

2 files changed

+135
-36
lines changed

2 files changed

+135
-36
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@
33
This incredibly simple script provides a HTTP server used to forward the request to one or more servers. For this you need to provide a DNS name in the env variable `TARGET_HOSTS_DNS_NAME`. For each ip address to which this hostname resolves, this script issues a request on the port defined in `TARGET_HOSTS_PORT` and the given method and path from the original request.
44

55
Use case for this is to forward a http trigger to all instances of a deployment in a kubernetes cluster in parallel. The idea is to use a headless service for this case to retrieve all pods ip addresses by querying the hostname of this headless service.
6+
7+
Additionally, you can specify the env variable `TARGET_ADDITIONAL_FORWARD_ENDPOINTS` to add additional endpoints, to which the request should be forwarded. It accepts a comma separated list of URL and method combinations like `http://your-endpoint:1234/test;POST`.
8+

app.js

+132-36
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,186 @@
11
const express = require('express');
2-
const http = require("http");
3-
2+
const http = require('http');
43
const dns = require('dns');
4+
55
const app = express();
66
const port = 3000;
77

8+
const forwardEndpoints = process.env.TARGET_ADDITIONAL_FORWARD_ENDPOINTS || '';
9+
10+
const parseEndpoints = (endpointsStr) => {
11+
const endpoints = [];
12+
if (endpointsStr) {
13+
endpointsStr.split(',').forEach((ep) => {
14+
const lastSemicolonIndex = ep.lastIndexOf(';');
15+
const url = ep.substring(0, lastSemicolonIndex);
16+
const method = ep.substring(lastSemicolonIndex + 1).toUpperCase();
17+
const parsedUrl = new URL(url);
18+
endpoints.push({
19+
hostname: parsedUrl.hostname,
20+
port: parsedUrl.port || 80,
21+
path: parsedUrl.pathname + parsedUrl.search,
22+
method,
23+
});
24+
});
25+
}
26+
return endpoints;
27+
};
28+
29+
const endpointsList = parseEndpoints(forwardEndpoints);
830

931
app.get('/health', (req, res, next) => {
1032
const target = process.env.TARGET_HOSTS_DNS_NAME;
11-
if (target == undefined) {
12-
return next(new Error("Environment variable TARGET_HOSTS_DNS_NAME is not set"))
33+
if (target === undefined) {
34+
return next(new Error("Environment variable TARGET_HOSTS_DNS_NAME is not set"));
1335
}
1436

1537
dns.lookup(target, { all: true }, (err, addresses) => {
16-
if (addresses == undefined) {
38+
if (addresses === undefined) {
1739
return next(new Error("Unable to resolve hostname '" + target + "'."));
1840
}
19-
res.send(addresses)
20-
})
21-
})
41+
res.send(addresses);
42+
});
43+
});
2244

2345
app.all('*', (req, res, next) => {
2446
dns.lookup(process.env.TARGET_HOSTS_DNS_NAME, { all: true }, (err, addresses) => {
25-
if (addresses == undefined) {
47+
if (addresses === undefined) {
2648
return next(new Error("Unable to resolve hostname '" + process.env.TARGET_HOSTS_DNS_NAME + "'."));
2749
}
2850

2951
const promises = [];
3052

31-
addresses.forEach(address => {
32-
promises.push(new Promise((res, rej) => {
33-
if (req.headers.host != undefined) {
53+
addresses.forEach((address) => {
54+
promises.push(new Promise((resolve, reject) => {
55+
if (req.headers.host !== undefined) {
3456
delete req.headers.host;
3557
}
3658
const options = {
3759
hostname: address.address,
3860
port: process.env.TARGET_HOSTS_PORT,
3961
path: req.url,
4062
method: req.method,
41-
headers: req.headers
42-
}
63+
headers: req.headers,
64+
};
4365

44-
console.log("Sending %s request to http://%s:%d%s with headers: %s", options.method, options.hostname, options.port, options.path, JSON.stringify(options.headers));
66+
console.log(
67+
"Sending %s request to http://%s:%d%s with headers: %s",
68+
options.method,
69+
options.hostname,
70+
options.port,
71+
options.path,
72+
JSON.stringify(options.headers)
73+
);
4574

4675
http
47-
.request(options, resp => {
76+
.request(options, (resp) => {
4877
const chunks = [];
49-
resp.on('data', data => chunks.push(data));
78+
resp.on('data', (data) => chunks.push(data));
5079
resp.on('end', () => {
5180
let resBody = Buffer.concat(chunks);
5281
switch (resp.headers['content-type']) {
5382
case 'application/json':
5483
resBody = JSON.parse(resBody);
5584
break;
5685
}
57-
console.log("Got response for call: http://%s:%d%s -> %d: %s", options.hostname, options.port, options.path, resp.statusCode, resp.statusMessage);
58-
res({
86+
console.log(
87+
"Got response for call: http://%s:%d%s -> %d: %s",
88+
options.hostname,
89+
options.port,
90+
options.path,
91+
resp.statusCode,
92+
resp.statusMessage
93+
);
94+
resolve({
5995
...options,
6096
statusCode: resp.statusCode,
6197
statusMessage: resp.statusMessage,
62-
body: resBody.toString()
63-
})
64-
65-
})
98+
body: resBody.toString(),
99+
});
100+
});
66101
})
67-
.on("error", err => {
68-
console.log("Error: " + err.message);
69-
// Promise.all does not work with errors
70-
res({
102+
.on('error', (err) => {
103+
console.log('Error: ' + err.message);
104+
resolve({
71105
...options,
72-
err: err
73-
})
106+
err: err,
107+
});
74108
})
75109
.end();
76110
}));
77111
});
78112

79-
Promise
80-
.all(promises)
113+
endpointsList.forEach((endpoint) => {
114+
promises.push(new Promise((resolve, reject) => {
115+
const options = {
116+
hostname: endpoint.hostname,
117+
port: endpoint.port,
118+
path: endpoint.path,
119+
method: endpoint.method,
120+
headers: req.headers,
121+
insecureHTTPParser: true
122+
};
123+
124+
console.log(
125+
"Sending %s request to http://%s:%d%s with headers: %s",
126+
options.method,
127+
options.hostname,
128+
options.port,
129+
options.path,
130+
JSON.stringify(options.headers)
131+
);
132+
133+
const reqForward = http.request(options, (resp) => {
134+
const chunks = [];
135+
resp.on('data', (data) => chunks.push(data));
136+
resp.on('end', () => {
137+
let resBody = Buffer.concat(chunks);
138+
switch (resp.headers['content-type']) {
139+
case 'application/json':
140+
resBody = JSON.parse(resBody);
141+
break;
142+
}
143+
console.log(
144+
"Got response for call: http://%s:%d%s -> %d: %s",
145+
options.hostname,
146+
options.port,
147+
options.path,
148+
resp.statusCode,
149+
resp.statusMessage
150+
);
151+
resolve({
152+
...options,
153+
statusCode: resp.statusCode,
154+
statusMessage: resp.statusMessage,
155+
body: resBody.toString(),
156+
});
157+
});
158+
});
159+
160+
reqForward.on('error', (err) => {
161+
console.log('Error: ' + err.message);
162+
resolve({
163+
...options,
164+
err: err,
165+
});
166+
});
167+
168+
if (req.body) {
169+
reqForward.write(JSON.stringify(req.body));
170+
}
171+
reqForward.end();
172+
}));
173+
});
174+
175+
Promise.all(promises)
81176
.then((data) => {
82-
console.log(data)
177+
console.log(data);
83178
res.send(data);
84-
}).catch(err => res.status(400).send(err));
179+
})
180+
.catch((err) => res.status(400).send(err));
85181
});
86-
})
182+
});
87183

88184
app.listen(port, '0.0.0.0', () => {
89-
console.log(`Listening on port ${port}...`)
90-
})
185+
console.log(`Listening on port ${port}...`);
186+
});

0 commit comments

Comments
 (0)