Skip to content

Commit 5ebe079

Browse files
Merge pull request #5783 from BitGo/WP-4029/external
feat(express): add keepAliveTimeout, headersTimeout option
2 parents f785ce5 + e014bbf commit 5ebe079

File tree

7 files changed

+123
-1
lines changed

7 files changed

+123
-1
lines changed

modules/express/.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ BITGO_EXTERNAL_SIGNER_URL=
1717
BITGO_SIGNER_MODE=
1818
BITGO_SIGNER_FILE_SYSTEM_PATH=
1919
BITGO_LIGHTNING_SIGNER_FILE_SYSTEM_PATH=
20+
BITGO_KEEP_ALIVE_TIMEOUT=
21+
BITGO_HEADERS_TIMEOUT=
2022

2123
# SIGN TRANSACTION FOR A SPECIFIC WALLET (Replace <ID> with your wallet ID)
2224
# See clientRoutes.ts => getWalletPwFromEnv()

modules/express/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,10 @@ This may be preferable for users who would like to apply their signature to thei
206206

207207
For more information please see our [External Signing Mode Documentation](EXTERNAL_SIGNER.md).
208208

209+
### KeepAliveTimeout/HeadersTimeout
210+
211+
If the deployment destination is configured with a load balancer such as ELB, a 504 Gateway Timeout may occur. In such cases, it might be resolved by configuring the settings so that LB keepAliveTimeout < keepAliveTimeout < headersTimeout. This can be achieved by specifying the keepAliveTimeout and headersTimeout options.
212+
209213
## Configuration Values
210214

211215
BitGo Express is able to take configuration options from either command line arguments, or via environment variables.
@@ -237,6 +241,9 @@ BitGo Express is able to take configuration options from either command line arg
237241

238242
\[0]: BitGo will also check the additional environment variables for some options for backwards compatibility, but these environment variables should be considered deprecated:
239243

244+
| N/A | --keepalivetimeout | `BITGO_KEEP_ALIVE_TIMEOUT` | N/A | BitGo Express server KeepAliveTimeout |
245+
| N/A | --headerstimeout | `BITGO_HEADERS_TIMEOUT` | N/A | BitGo Express server HeadersTimeout |
246+
240247
- Disable SSL
241248
- `DISABLESSL`
242249
- `DISABLE_SSL`

modules/express/src/args.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,12 @@ parser.addArgument(['--signerFileSystemPath'], {
109109
parser.addArgument(['--lightningSignerFileSystemPath'], {
110110
help: 'Local path specifying where an Express machine keeps lightning signer urls.',
111111
});
112+
113+
parser.addArgument(['--keepalivetimeout'], {
114+
help: 'keep alive timeout in milliseconds',
115+
});
116+
117+
parser.addArgument(['--headerstimeout'], {
118+
help: 'headers timeout in milliseconds',
119+
});
112120
export const args = () => parser.parseArgs();

modules/express/src/config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ export interface Config {
4141
signerMode?: boolean;
4242
signerFileSystemPath?: string;
4343
lightningSignerFileSystemPath?: string;
44+
keepAliveTimeout?: number;
45+
headersTimeout?: number;
4446
}
4547

4648
export const ArgConfig = (args): Partial<Config> => ({
@@ -65,6 +67,8 @@ export const ArgConfig = (args): Partial<Config> => ({
6567
signerMode: args.signerMode,
6668
signerFileSystemPath: args.signerFileSystemPath,
6769
lightningSignerFileSystemPath: args.lightningSignerFileSystemPath,
70+
keepAliveTimeout: args.keepalivetimeout,
71+
headersTimeout: args.headerstimeout,
6872
});
6973

7074
export const EnvConfig = (): Partial<Config> => ({
@@ -89,6 +93,8 @@ export const EnvConfig = (): Partial<Config> => ({
8993
signerMode: readEnvVar('BITGO_SIGNER_MODE') ? true : undefined,
9094
signerFileSystemPath: readEnvVar('BITGO_SIGNER_FILE_SYSTEM_PATH'),
9195
lightningSignerFileSystemPath: readEnvVar('BITGO_LIGHTNING_SIGNER_FILE_SYSTEM_PATH'),
96+
keepAliveTimeout: Number(readEnvVar('BITGO_KEEP_ALIVE_TIMEOUT')),
97+
headersTimeout: Number(readEnvVar('BITGO_HEADERS_TIMEOUT')),
9298
});
9399

94100
export const DefaultConfig: Config = {
@@ -173,6 +179,8 @@ function mergeConfigs(...configs: Partial<Config>[]): Config {
173179
signerMode: get('signerMode'),
174180
signerFileSystemPath: get('signerFileSystemPath'),
175181
lightningSignerFileSystemPath: get('lightningSignerFileSystemPath'),
182+
keepAliveTimeout: get('keepAliveTimeout'),
183+
headersTimeout: get('headersTimeout'),
176184
};
177185
}
178186

modules/express/src/expressApp.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,16 @@ function isTLS(config: Config): config is Config & { keyPath: string; crtPath: s
153153
* Create either a HTTP or HTTPS server
154154
*/
155155
export async function createServer(config: Config, app: express.Application): Promise<https.Server | http.Server> {
156-
return isTLS(config) ? await createHttpsServer(app, config) : createHttpServer(app);
156+
const server = isTLS(config) ? await createHttpsServer(app, config) : createHttpServer(app);
157+
158+
// Set keepAliveTimeout and headersTimeout if specified in config
159+
if (config.keepAliveTimeout !== undefined) {
160+
server.keepAliveTimeout = config.keepAliveTimeout;
161+
}
162+
if (config.headersTimeout !== undefined) {
163+
server.headersTimeout = config.headersTimeout;
164+
}
165+
return server;
157166
}
158167

159168
/**

modules/express/test/unit/bitgoExpress.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,5 +559,87 @@ describe('Bitgo Express', function () {
559559
(() => expressApp(args)).should.not.throw();
560560
readValidStub.restore();
561561
});
562+
563+
it('should set keepAliveTimeout and headersTimeout if specified in config for HTTP server', async function () {
564+
const createServerStub = sinon.stub(http, 'createServer').callsFake(() => {
565+
return { listen: sinon.stub(), setTimeout: sinon.stub() } as unknown as http.Server;
566+
});
567+
568+
const args: any = {
569+
env: 'test',
570+
bind: 'localhost',
571+
keepAliveTimeout: 5000,
572+
headersTimeout: 10000,
573+
};
574+
575+
const server = await createServer(args, null as any);
576+
577+
server.keepAliveTimeout.should.equal(args.keepAliveTimeout);
578+
server.headersTimeout.should.equal(args.headersTimeout);
579+
580+
createServerStub.restore();
581+
});
582+
583+
it('should set keepAliveTimeout and headersTimeout if specified in config for HTTPS server', async function () {
584+
const createServerStub = sinon.stub(https, 'createServer').callsFake(() => {
585+
return { listen: sinon.stub(), setTimeout: sinon.stub() } as unknown as https.Server;
586+
});
587+
588+
const args: any = {
589+
env: 'test',
590+
bind: 'localhost',
591+
sslKey: 'ssl-key',
592+
sslCert: 'ssl-cert',
593+
keepAliveTimeout: 5000,
594+
headersTimeout: 10000,
595+
};
596+
597+
const server = await createServer(args, null as any);
598+
599+
server.keepAliveTimeout.should.equal(args.keepAliveTimeout);
600+
server.headersTimeout.should.equal(args.headersTimeout);
601+
602+
createServerStub.restore();
603+
});
604+
605+
it('should not set keepAliveTimeout and headersTimeout if not specified in config for HTTP server', async function () {
606+
const createServerStub = sinon.stub(http, 'createServer').callsFake(() => {
607+
return { listen: sinon.stub(), setTimeout: sinon.stub() } as unknown as http.Server;
608+
});
609+
610+
const args: any = {
611+
env: 'test',
612+
bind: 'localhost',
613+
// keepAliveTimeout and headersTimeout are not specified
614+
};
615+
616+
const server = await createServer(args, null as any);
617+
618+
should(server.keepAliveTimeout).be.undefined();
619+
should(server.headersTimeout).be.undefined();
620+
621+
createServerStub.restore();
622+
});
623+
624+
it('should not set keepAliveTimeout and headersTimeout if not specified in config for HTTPS server', async function () {
625+
const createServerStub = sinon.stub(https, 'createServer').callsFake(() => {
626+
return { listen: sinon.stub(), setTimeout: sinon.stub() } as unknown as https.Server;
627+
});
628+
629+
const args: any = {
630+
env: 'test',
631+
bind: 'localhost',
632+
sslKey: 'ssl-key',
633+
sslCert: 'ssl-cert',
634+
// keepAliveTimeout and headersTimeout are not specified
635+
};
636+
637+
const server = await createServer(args, null as any);
638+
639+
should(server.keepAliveTimeout).be.undefined();
640+
should(server.headersTimeout).be.undefined();
641+
642+
createServerStub.restore();
643+
});
562644
});
563645
});

modules/express/test/unit/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ describe('Config:', () => {
8686
signerMode: 'argsignerMode',
8787
signerFileSystemPath: 'argsignerFileSystemPath',
8888
lightningSignerFileSystemPath: 'arglightningSignerFileSystemPath',
89+
keepalivetimeout: 'argkeepalivetimeout',
90+
headerstimeout: 'argheaderstimeout',
8991
});
9092
const envStub = sinon.stub(process, 'env').value({
9193
BITGO_PORT: 'env12345',
@@ -108,6 +110,8 @@ describe('Config:', () => {
108110
BITGO_SIGNER_MODE: 'envsignerMode',
109111
BITGO_SIGNER_FILE_SYSTEM_PATH: 'envsignerFileSystemPath',
110112
BITGO_LIGHTNING_SIGNER_FILE_SYSTEM_PATH: 'envlightningSignerFileSystemPath',
113+
BITGO_KEEP_ALIVE_TIMETOUT: 'envkeepalivetimeout',
114+
BITGO_HEADERS_TIMETOUT: 'envheaderstimeout',
111115
});
112116
config().should.eql({
113117
port: 23456,
@@ -131,6 +135,8 @@ describe('Config:', () => {
131135
signerMode: 'argsignerMode',
132136
signerFileSystemPath: 'argsignerFileSystemPath',
133137
lightningSignerFileSystemPath: 'arglightningSignerFileSystemPath',
138+
keepAliveTimeout: 'argkeepalivetimeout',
139+
headersTimeout: 'argheaderstimeout',
134140
});
135141
argStub.restore();
136142
envStub.restore();

0 commit comments

Comments
 (0)