Skip to content

Commit

Permalink
feat(http): http timeout options
Browse files Browse the repository at this point in the history
adds maxHeadersCount, maxRequestsPerSocket, timeout, headersTimeout, keepAliveTimeout, requestTimeout to http config and sets them on Node's http server on creation/start.
  • Loading branch information
marcj committed Jun 19, 2024
1 parent d447c1d commit 44fbf56
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 16 deletions.
4 changes: 2 additions & 2 deletions packages/framework/src/application-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ export const onServerWorkerShutdown = new EventToken('server.worker.shutdown', S

type ApplicationServerConfig = Pick<FrameworkConfig, 'server' | 'port' | 'host' | 'httpsPort' |
'ssl' | 'sslKey' | 'sslCertificate' | 'sslCa' | 'sslCrl' |
'varPath' | 'selfSigned' | 'keepAliveTimeout' | 'workers' | 'publicDir' |
'debug' | 'debugUrl' | 'gracefulShutdownTimeout' | 'compression'>;
'varPath' | 'selfSigned' | 'workers' | 'publicDir' |
'debug' | 'debugUrl' | 'gracefulShutdownTimeout' | 'compression' | 'http'>;

function needsHttpWorker(config: { publicDir?: string }, rpcControllers: RpcControllers, router: HttpRouter) {
return Boolean(config.publicDir || rpcControllers.controllers.size || router.getRoutes().length);
Expand Down
10 changes: 7 additions & 3 deletions packages/framework/src/module.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*
* You should have received a copy of the MIT License along with this program.
*/
import { HttpParserOptions } from '@deepkit/http';
import { HttpConfig, HttpParserOptions } from '@deepkit/http';

const isWindows = 'undefined' !== typeof process ? process.platform === 'win32' : false;

Expand Down Expand Up @@ -41,8 +41,6 @@ export class FrameworkConfig {
*/
selfSigned?: boolean;

keepAliveTimeout?: number;

path: string = '/';

/**
Expand Down Expand Up @@ -157,4 +155,10 @@ export class FrameworkConfig {
migrationDir: string = 'migrations';

broker: BrokerConfig = new BrokerConfig;

/**
* Will be forwarded to HttpModule.
* @see HttpConfig
*/
http: HttpConfig = new HttpConfig;
}
4 changes: 3 additions & 1 deletion packages/framework/src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ export class FrameworkModule extends createModule({
this.addListener(HttpLogger);
}

this.getImportedModuleByClass(HttpModule).configure({ parser: this.config.httpParse });
this.getImportedModuleByClass(HttpModule).configure({ ...this.config.http, parser: this.config.httpParse });

if (this.config.publicDir) {
const localPublicDir = join(process.cwd(), this.config.publicDir);
Expand Down Expand Up @@ -230,6 +230,7 @@ export class FrameworkModule extends createModule({

if (this.config.httpRpcBasePath) {
const rpcBaseUrl = this.config.httpRpcBasePath;

@http.controller(rpcBaseUrl)
class HttpRpcController {
constructor(protected rpcKernel: RpcKernel) {
Expand Down Expand Up @@ -262,6 +263,7 @@ export class FrameworkModule extends createModule({
return response;
}
}

this.addController(HttpRpcController);
}

Expand Down
30 changes: 20 additions & 10 deletions packages/framework/src/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import type { Server as WebSocketServer, ServerOptions as WebSocketServerOptions
import ws from 'ws';
import selfsigned from 'selfsigned';

import { HttpKernel, HttpRequest, HttpResponse } from '@deepkit/http';
import { HttpConfig, HttpKernel, HttpRequest, HttpResponse } from '@deepkit/http';
import { InjectorContext } from '@deepkit/injector';
import { RpcControllers, RpcInjectorContext } from './rpc.js';
import { SecureContextOptions, TlsOptions } from 'tls';
Expand Down Expand Up @@ -58,10 +58,7 @@ export interface WebServerOptions {
*/
httpsPort?: number;

/**
* HTTP Keep alive timeout.
*/
keepAliveTimeout?: number;
http: HttpConfig;

/**
* When external server should be used. If this is set, all other options are ignored.
Expand Down Expand Up @@ -242,6 +239,16 @@ export class WebWorker {
return this.httpKernel.handleRequest(request, response);
}

applyServerSettings(server: Server) {
const config = this.options.http;
if ('undefined' !== typeof config.timeout) server.timeout = config.timeout;
if ('undefined' !== typeof config.requestTimeout) server.requestTimeout = config.requestTimeout;
if ('undefined' !== typeof config.headersTimeout) server.headersTimeout = config.headersTimeout;
if ('undefined' !== typeof config.maxHeadersCount) server.maxHeadersCount = config.maxHeadersCount;
if ('undefined' !== typeof config.keepAliveTimeout) server.keepAliveTimeout = config.keepAliveTimeout;
if ('undefined' !== typeof config.maxRequestsPerSocket) server.maxRequestsPerSocket = config.maxRequestsPerSocket;
}

start() {
if (this.options.server) {
this.server = this.options.server as Server;
Expand Down Expand Up @@ -277,8 +284,8 @@ export class WebWorker {
Object.assign({ IncomingMessage: HttpRequest, ServerResponse: HttpResponse as any, }, options),
this.handleRequest as any
);
this.servers.requestTimeout
this.servers.listen(this.options.httpsPort || this.options.port, this.options.host);
if (this.options.keepAliveTimeout) this.servers.keepAliveTimeout = this.options.keepAliveTimeout;
}

const startHttpServer = !this.servers || (this.servers && this.options.httpsPort);
Expand All @@ -287,10 +294,13 @@ export class WebWorker {
{ IncomingMessage: HttpRequest, ServerResponse: HttpResponse as any },
this.handleRequest as any
);
if (this.options.keepAliveTimeout) this.server.keepAliveTimeout = this.options.keepAliveTimeout;
this.server.listen(this.options.port, this.options.host);
}
}

if (this.servers) this.applyServerSettings(this.servers);
if (this.server) this.applyServerSettings(this.server);

this.startRpc();
}

Expand Down Expand Up @@ -335,10 +345,10 @@ export class WebWorker {
await sleep(0.1);
}
}
if (this.rpcListener) await this.rpcListener.close();
if (this.server) this.server.close();
if (this.servers) this.servers.close();
}
if (this.rpcListener) await this.rpcListener.close();
if (this.server) this.server.close();
if (this.servers) this.servers.close();
}
}

Expand Down
74 changes: 74 additions & 0 deletions packages/http/src/module.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,80 @@ export interface HttpParserOptions {
}

export class HttpConfig {

debug: boolean = false;

parser: HttpParserOptions = {};

/**
* Limits maximum incoming headers count. If set to 0, no limit will be applied.
*/
maxHeadersCount?: number;

/**
* The maximum number of requests socket can handle
* before closing keep alive connection.
*
* A value of `0` will disable the limit.
*
* When the limit is reached it will set the `Connection` header value to `close`,
* but will not actually close the connection, subsequent requests sent
* after the limit is reached will get `503 Service Unavailable` as a response.
*/
maxRequestsPerSocket?: number;

/**
* The number of milliseconds of inactivity before a socket is presumed
* to have timed out.
*
* A value of `0` will disable the timeout behavior on incoming connections.
*
* The socket timeout logic is set up on connection, so changing this
* value only affects new connections to the server, not any existing connections.
*/
timeout?: number;

/**
* Limit the amount of time the parser will wait to receive the complete HTTP
* headers.
*
* If the timeout expires, the server responds with status 408 without
* forwarding the request to the request listener and then closes the connection.
*
* It must be set to a non-zero value (e.g. 120 seconds) to protect against
* potential Denial-of-Service attacks in case the server is deployed without a
* reverse proxy in front.
*/
headersTimeout?: number;

/**
* The number of milliseconds of inactivity a server needs to wait for additional
* incoming data, after it has finished writing the last response, before a socket
* will be destroyed. If the server receives new data before the keep-alive
* timeout has fired, it will reset the regular inactivity timeout, i.e., `server.timeout`.
*
* A value of `0` will disable the keep-alive timeout behavior on incoming
* connections.
* A value of `0` makes the http server behave similarly to Node.js versions prior
* to 8.0.0, which did not have a keep-alive timeout.
*
* The socket timeout logic is set up on connection, so changing this value only
* affects new connections to the server, not any existing connections.
*/
keepAliveTimeout?: number;

/**
* Sets the timeout value in milliseconds for receiving the entire request from
* the client.
*
* If the timeout expires, the server responds with status 408 without
* forwarding the request to the request listener and then closes the connection.
*
* It must be set to a non-zero value (e.g. 120 seconds) to protect against
* potential Denial-of-Service attacks in case the server is deployed without a
* reverse proxy in front.
*
* Default is 5 minutes.
*/
requestTimeout?: number;
}

0 comments on commit 44fbf56

Please sign in to comment.