feat(http): support server-level timeout and socket options via app-config
Adds support for configuring server-level HTTP options through the `app-config.yaml` file under the `backend.server` key. This includes support for: `headersTimeout`, `keepAliveTimeout`, `requestTimeout`, `timeout`, `maxHeadersCount`, and `maxRequestsPerSocket`. These options are passed directly to the underlying Node.js HTTP server, when omitted, the default values are used. Refs: https://github.com/backstage/backstage/issues/21808 Refs: https://github.com/backstage/backstage/issues/30449 Signed-off-by: Beth Griggs <bethanyngriggs@gmail.com>
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
---
|
||||
'@backstage/backend-defaults': minor
|
||||
---
|
||||
|
||||
Adds support for configuring server-level HTTP options through the
|
||||
`app-config.yaml` file under the `backend.server` key. Supported options
|
||||
include `headersTimeout`, `keepAliveTimeout`, `requestTimeout`, `timeout`,
|
||||
`maxHeadersCount`, and `maxRequestsPerSocket`.
|
||||
|
||||
These are passed directly to the underlying Node.js HTTP server.
|
||||
If omitted, Node.js defaults are used.
|
||||
@@ -65,6 +65,14 @@ backend:
|
||||
# - A standard ISO formatted duration string, e.g. 'P2DT6H' or 'PT1M'.
|
||||
# - An object with individual units (in plural) as keys, e.g. `{ days: 2, hours: 6 }`.
|
||||
serverShutdownDelay: { seconds: 20 }
|
||||
server:
|
||||
# (Optional) HTTP server configuration, Node.js defaults apply otherwise
|
||||
headersTimeout: 60000
|
||||
keepAliveTimeout: 5000
|
||||
maxHeadersCount: 2000
|
||||
maxRequestsPerSocket: 100
|
||||
requestTimeout: 30000
|
||||
timeout: 30000
|
||||
```
|
||||
|
||||
### Via Code
|
||||
|
||||
@@ -61,6 +61,59 @@ describe('readHttpServerOptions', () => {
|
||||
expect(readHttpServerOptions(new ConfigReader(input))).toEqual(output);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[
|
||||
{
|
||||
server: {
|
||||
headersTimeout: 10000,
|
||||
requestTimeout: 30000,
|
||||
keepAliveTimeout: 5000,
|
||||
timeout: 60000,
|
||||
maxHeadersCount: 1000,
|
||||
maxRequestsPerSocket: 100,
|
||||
},
|
||||
},
|
||||
{
|
||||
listen: { host: '', port: 7007 },
|
||||
https: undefined,
|
||||
serverOptions: {
|
||||
headersTimeout: 10000,
|
||||
requestTimeout: 30000,
|
||||
keepAliveTimeout: 5000,
|
||||
timeout: 60000,
|
||||
maxHeadersCount: 1000,
|
||||
maxRequestsPerSocket: 100,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
server: {
|
||||
keepAliveTimeout: 8000,
|
||||
timeout: 30000,
|
||||
},
|
||||
},
|
||||
{
|
||||
listen: { host: '', port: 7007 },
|
||||
https: undefined,
|
||||
serverOptions: {
|
||||
keepAliveTimeout: 8000,
|
||||
timeout: 30000,
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
{ server: {} },
|
||||
{
|
||||
listen: { host: '', port: 7007 },
|
||||
https: undefined,
|
||||
serverOptions: undefined,
|
||||
},
|
||||
],
|
||||
])('should read server options %#', (input, output) => {
|
||||
expect(readHttpServerOptions(new ConfigReader(input))).toEqual(output);
|
||||
});
|
||||
|
||||
it.each([
|
||||
[
|
||||
{ listen: { port: 'not-a-number' } },
|
||||
|
||||
@@ -38,6 +38,7 @@ export function readHttpServerOptions(config?: Config): HttpServerOptions {
|
||||
return {
|
||||
listen: readHttpListenOptions(config),
|
||||
https: readHttpsOptions(config),
|
||||
serverOptions: readServerOptions(config),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -99,3 +100,32 @@ function readHttpsOptions(config?: Config): HttpServerOptions['https'] {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function readServerOptions(
|
||||
config?: Config,
|
||||
): HttpServerOptions['serverOptions'] {
|
||||
const serverConfig = config?.getOptionalConfig('server');
|
||||
if (!serverConfig) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const serverOptions: HttpServerOptions['serverOptions'] = {};
|
||||
|
||||
const keys = [
|
||||
'headersTimeout',
|
||||
'requestTimeout',
|
||||
'keepAliveTimeout',
|
||||
'timeout',
|
||||
'maxHeadersCount',
|
||||
'maxRequestsPerSocket',
|
||||
] as const;
|
||||
|
||||
for (const key of keys) {
|
||||
const value = serverConfig.getOptionalNumber(key);
|
||||
if (value !== undefined) {
|
||||
serverOptions[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(serverOptions).length === 0 ? undefined : serverOptions;
|
||||
}
|
||||
|
||||
@@ -84,6 +84,8 @@ async function createServer(
|
||||
options: HttpServerOptions,
|
||||
deps: { logger: LoggerService },
|
||||
): Promise<http.Server> {
|
||||
let server: http.Server;
|
||||
|
||||
if (options.https) {
|
||||
const { certificate } = options.https;
|
||||
if (certificate.type === 'generated') {
|
||||
@@ -91,10 +93,31 @@ async function createServer(
|
||||
certificate.hostname,
|
||||
deps.logger,
|
||||
);
|
||||
return https.createServer(credentials, listener);
|
||||
server = https.createServer(credentials, listener);
|
||||
} else {
|
||||
server = https.createServer(certificate, listener);
|
||||
}
|
||||
return https.createServer(certificate, listener);
|
||||
} else {
|
||||
server = http.createServer(listener);
|
||||
}
|
||||
|
||||
return http.createServer(listener);
|
||||
// apply custom server options
|
||||
if (options.serverOptions) {
|
||||
const { serverOptions } = options;
|
||||
|
||||
if (serverOptions.headersTimeout !== undefined)
|
||||
server.headersTimeout = serverOptions.headersTimeout;
|
||||
if (serverOptions.requestTimeout !== undefined)
|
||||
server.requestTimeout = serverOptions.requestTimeout;
|
||||
if (serverOptions.keepAliveTimeout !== undefined)
|
||||
server.keepAliveTimeout = serverOptions.keepAliveTimeout;
|
||||
if (serverOptions.timeout !== undefined)
|
||||
server.timeout = serverOptions.timeout;
|
||||
if (serverOptions.maxHeadersCount !== undefined)
|
||||
server.maxHeadersCount = serverOptions.maxHeadersCount;
|
||||
if (serverOptions.maxRequestsPerSocket !== undefined)
|
||||
server.maxRequestsPerSocket = serverOptions.maxRequestsPerSocket;
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,14 @@ export type HttpServerOptions = {
|
||||
https?: {
|
||||
certificate: HttpServerCertificateOptions;
|
||||
};
|
||||
serverOptions?: {
|
||||
headersTimeout?: number;
|
||||
requestTimeout?: number;
|
||||
keepAliveTimeout?: number;
|
||||
timeout?: number;
|
||||
maxHeadersCount?: number;
|
||||
maxRequestsPerSocket?: number;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user