Skip to content

Commit 4f4cede

Browse files
committed
feat: add support for keepAlive
1 parent 2e76013 commit 4f4cede

File tree

3 files changed

+125
-94
lines changed

3 files changed

+125
-94
lines changed

lib/proxy.mjs

+90-92
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,6 @@ export class Redbird {
6767

6868
this._defaultResolver.priority = 0;
6969

70-
const _this = this;
71-
7270
if ((opts.cluster && typeof opts.cluster !== 'number') || opts.cluster > 32) {
7371
throw Error('cluster setting must be an integer less than 32');
7472
}
@@ -103,6 +101,25 @@ export class Redbird {
103101
this.addResolver(opts.resolvers);
104102
}
105103

104+
const websocketsUpgrade = async (req, socket, head) => {
105+
socket.on('error', function (err) {
106+
log && log.error(err, 'WebSockets error');
107+
});
108+
const src = this._getSource(req);
109+
const target = await this._getTarget(src, req);
110+
111+
log && log.info({ headers: req.headers, target: target }, 'upgrade to websockets');
112+
113+
if (target) {
114+
if (target.useTargetHostHeader === true) {
115+
req.headers.host = target.host;
116+
}
117+
proxy.ws(req, socket, head, { target });
118+
} else {
119+
respondNotFound(req, socket);
120+
}
121+
};
122+
106123
//
107124
// Routing table.
108125
//
@@ -111,17 +128,21 @@ export class Redbird {
111128
//
112129
// Create a proxy server with custom application logic
113130
//
131+
let agent;
132+
133+
if (opts.keepAlive) {
134+
agent = this.agent = new http.Agent({
135+
keepAlive: true,
136+
});
137+
}
138+
114139
const proxy = (this.proxy = httpProxy.createProxyServer({
115140
xfwd: opts.xfwd != false,
116141
prependPath: false,
117142
secure: opts.secure !== false,
118143
timeout: opts.timeout,
119144
proxyTimeout: opts.proxyTimeout,
120-
/*
121-
agent: new http.Agent({
122-
keepAlive: true
123-
})
124-
*/
145+
agent,
125146
}));
126147

127148
proxy.on('proxyReq', function (p, req) {
@@ -145,8 +166,8 @@ export class Redbird {
145166
//
146167
if (opts.ssl) {
147168
if (_.isArray(opts.ssl)) {
148-
opts.ssl.forEach(function (sslOpts) {
149-
_this.setupHttpsProxy(proxy, websocketsUpgrade, log, sslOpts);
169+
opts.ssl.forEach((sslOpts) => {
170+
this.setupHttpsProxy(proxy, websocketsUpgrade, log, sslOpts);
150171
});
151172
} else {
152173
this.setupHttpsProxy(proxy, websocketsUpgrade, log, opts.ssl);
@@ -156,7 +177,7 @@ export class Redbird {
156177
//
157178
// Plain HTTP Proxy
158179
//
159-
const server = this.setupHttpProxy(proxy, websocketsUpgrade, log, opts);
180+
const server = (this.server = this.setupHttpProxy(proxy, websocketsUpgrade, log, opts));
160181

161182
server.listen(opts.port, opts.host);
162183

@@ -169,24 +190,6 @@ export class Redbird {
169190
log && log.info('Started a Redbird reverse proxy server on port %s', opts.port);
170191
}
171192

172-
function websocketsUpgrade(req, socket, head) {
173-
socket.on('error', function (err) {
174-
log && log.error(err, 'WebSockets error');
175-
});
176-
const src = _this._getSource(req);
177-
_this._getTarget(src, req).then(function (target) {
178-
log && log.info({ headers: req.headers, target: target }, 'upgrade to websockets');
179-
if (target) {
180-
if (target.useTargetHostHeader === true) {
181-
req.headers.host = target.host;
182-
}
183-
proxy.ws(req, socket, head, { target: target });
184-
} else {
185-
respondNotFound(req, socket);
186-
}
187-
});
188-
}
189-
190193
function handleProxyError(err, req, res) {
191194
//
192195
// Send a 500 http status if headers have been sent
@@ -214,7 +217,7 @@ export class Redbird {
214217

215218
setupHttpProxy(proxy, websocketsUpgrade, log, opts) {
216219
const httpServerModule = opts.serverModule || http;
217-
const server = (this.server = httpServerModule.createServer((req, res) => {
220+
const server = httpServerModule.createServer((req, res) => {
218221
const src = this._getSource(req);
219222
this._getTarget(src, req, res).then((target) => {
220223
if (target) {
@@ -230,7 +233,7 @@ export class Redbird {
230233
respondNotFound(req, res);
231234
}
232235
});
233-
}));
236+
});
234237

235238
//
236239
// Listen to the `upgrade` event and proxy the
@@ -265,7 +268,6 @@ export class Redbird {
265268
}
266269

267270
setupHttpsProxy(proxy, websocketsUpgrade, log, sslOpts) {
268-
const _this = this;
269271
let https;
270272

271273
this.certs = this.certs || {};
@@ -309,18 +311,17 @@ export class Redbird {
309311
https = sslOpts.serverModule || require('https');
310312
}
311313

312-
const httpsServer = (this.httpsServer = https.createServer(ssl, function (req, res) {
313-
const src = _this._getSource(req);
314-
const httpProxyOpts = Object.assign({}, _this.opts.httpProxy);
314+
const httpsServer = (this.httpsServer = https.createServer(ssl, async (req, res) => {
315+
const src = this._getSource(req);
316+
const httpProxyOpts = Object.assign({}, this.opts.httpProxy);
315317

316-
_this._getTarget(src, req, res).then(function (target) {
317-
if (target) {
318-
httpProxyOpts.target = target;
319-
proxy.web(req, res, httpProxyOpts);
320-
} else {
321-
respondNotFound(req, res);
322-
}
323-
});
318+
const target = await this._getTarget(src, req, res);
319+
if (target) {
320+
httpProxyOpts.target = target;
321+
proxy.web(req, res, httpProxyOpts);
322+
} else {
323+
respondNotFound(req, res);
324+
}
324325
}));
325326

326327
httpsServer.on('upgrade', websocketsUpgrade);
@@ -344,8 +345,7 @@ export class Redbird {
344345
resolver = [resolver];
345346
}
346347

347-
const _this = this;
348-
resolver.forEach(function (resolveObj) {
348+
resolver.forEach((resolveObj) => {
349349
if (!_.isFunction(resolveObj)) {
350350
throw new Error('Resolver must be an invokable function.');
351351
}
@@ -354,10 +354,10 @@ export class Redbird {
354354
resolveObj.priority = 0;
355355
}
356356

357-
_this.resolvers.push(resolveObj);
357+
this.resolvers.push(resolveObj);
358358
});
359359

360-
_this.resolvers = _.sortBy(_.uniq(_this.resolvers), ['priority']).reverse();
360+
this.resolvers = _.sortBy(_.uniq(this.resolvers), ['priority']).reverse();
361361
}
362362

363363
removeResolver(resolver) {
@@ -453,48 +453,47 @@ export class Redbird {
453453
return this;
454454
}
455455

456-
updateCertificates(domain, email, production, renewWithin, renew) {
457-
const _this = this;
458-
return letsencrypt.getCertificates(domain, email, production, renew, this.log).then(
459-
function (certs) {
460-
if (certs) {
461-
const opts = {
462-
key: certs.privkey,
463-
cert: certs.cert + certs.chain,
464-
};
465-
_this.certs[domain] = tls.createSecureContext(opts).context;
466-
467-
//
468-
// TODO: cluster friendly
469-
//
470-
let renewTime = certs.expiresAt - Date.now() - renewWithin;
471-
renewTime =
472-
renewTime > 0 ? renewTime : _this.opts.letsencrypt.minRenewTime || 60 * 60 * 1000;
473-
474-
_this.log &&
475-
_this.log.info('Renewal of %s in %s days', domain, Math.floor(renewTime / ONE_DAY));
476-
477-
function renewCertificate() {
478-
_this.log && _this.log.info('Renewing letscrypt certificates for %s', domain);
479-
_this.updateCertificates(domain, email, production, renewWithin, true);
480-
}
456+
async updateCertificates(domain, email, production, renewWithin, renew) {
457+
try {
458+
const certs = await letsencrypt.getCertificates(domain, email, production, renew, this.log);
459+
if (certs) {
460+
const opts = {
461+
key: certs.privkey,
462+
cert: certs.cert + certs.chain,
463+
};
464+
this.certs[domain] = tls.createSecureContext(opts).context;
481465

482-
_this.certs[domain].renewalTimeout = safe.setTimeout(renewCertificate, renewTime);
483-
} else {
484-
//
485-
// TODO: Try again, but we need an exponential backof to avoid getting banned.
486-
//
487-
_this.log && _this.log.info('Could not get any certs for %s', domain);
488-
}
489-
},
490-
function (err) {
491-
console.error('Error getting LetsEncrypt certificates', err);
466+
//
467+
// TODO: cluster friendly
468+
//
469+
let renewTime = certs.expiresAt - Date.now() - renewWithin;
470+
renewTime =
471+
renewTime > 0 ? renewTime : this.opts.letsencrypt.minRenewTime || 60 * 60 * 1000;
472+
473+
this.log &&
474+
this.log.info('Renewal of %s in %s days', domain, Math.floor(renewTime / ONE_DAY));
475+
476+
const renewCertificate = () => {
477+
this.log && this.log.info('Renewing letscrypt certificates for %s', domain);
478+
this.updateCertificates(domain, email, production, renewWithin, true);
479+
};
480+
481+
this.certs[domain].renewalTimeout = safe.setTimeout(renewCertificate, renewTime);
482+
} else {
483+
//
484+
// TODO: Try again, but we need an exponential backof to avoid getting banned.
485+
//
486+
this.log && this.log.info('Could not get any certs for %s', domain);
492487
}
493-
);
488+
} catch (err) {
489+
console.error('Error getting LetsEncrypt certificates', err);
490+
}
494491
}
495492

496493
unregister(src, target) {
497-
if (this.opts.cluster && cluster.isMaster) return this;
494+
if (this.opts.cluster && cluster.isPrimary) {
495+
return this;
496+
}
498497

499498
if (!src) {
500499
return this;
@@ -649,15 +648,14 @@ export class Redbird {
649648
}
650649

651650
close() {
652-
try {
653-
return Promise.all(
654-
[this.server, this.httpsServer]
655-
.filter((s) => s)
656-
.map((server) => new Promise((resolve) => server.close(resolve)))
657-
);
658-
} catch (err) {
659-
// Ignore for now...
660-
}
651+
this.proxy.close();
652+
this.agent && this.agent.destroy();
653+
654+
return Promise.all(
655+
[this.server, this.httpsServer]
656+
.filter((s) => s)
657+
.map((server) => new Promise((resolve) => server.close(resolve)))
658+
);
661659
}
662660

663661
//

samples/sample1.mjs

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use strict';
2+
3+
import { createServer } from 'http';
4+
import { Redbird } from '../index.mjs';
5+
import cluster from 'cluster';
6+
7+
async function sample1() {
8+
const proxy = new Redbird({
9+
port: 8080,
10+
bunyan: false,
11+
cluster: 4,
12+
});
13+
14+
proxy.register({
15+
src: 'http://localhost',
16+
target: 'localhost:3000/test',
17+
onRequest: (req, res, target) => {
18+
req.headers.foo = 'bar';
19+
delete req.headers.blah;
20+
},
21+
});
22+
}
23+
24+
sample1();
25+
26+
if (!cluster.isPrimary) {
27+
createServer(function (req, res) {
28+
res.writeHead(200);
29+
res.write('hello world');
30+
res.end();
31+
}).listen(3000);
32+
}

test/onrequest.spec.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,18 @@ describe('onRequest hook', function () {
3535
blah: 'xyz',
3636
},
3737
});
38+
3839
expect(res.status).to.equal(200);
3940
expect(target).to.exist;
4041
expect(saveProxyHeaders).to.exist;
4142
expect(saveProxyHeaders.blah).to.equal('xyz');
4243

44+
await proxy.close();
45+
4346
const req = await promiseServer;
4447
expect(req).to.exist;
4548
expect(req.headers.foo).to.equal('bar');
4649
expect(req.headers.blah).to.equal(undefined);
47-
48-
await proxy.close();
4950
});
5051
});
5152

0 commit comments

Comments
 (0)