diff --git a/packages/framework/src/application-server.ts b/packages/framework/src/application-server.ts index 802ff8e2f..caf31c28a 100644 --- a/packages/framework/src/application-server.ts +++ b/packages/framework/src/application-server.ts @@ -78,8 +78,8 @@ export const onServerWorkerShutdown = new EventToken('server.worker.shutdown', S type ApplicationServerConfig = Pick; + '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); diff --git a/packages/framework/src/module.config.ts b/packages/framework/src/module.config.ts index c8bca7451..68718e1bd 100644 --- a/packages/framework/src/module.config.ts +++ b/packages/framework/src/module.config.ts @@ -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; @@ -41,8 +41,6 @@ export class FrameworkConfig { */ selfSigned?: boolean; - keepAliveTimeout?: number; - path: string = '/'; /** @@ -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; } diff --git a/packages/framework/src/module.ts b/packages/framework/src/module.ts index 47b114942..360896da0 100644 --- a/packages/framework/src/module.ts +++ b/packages/framework/src/module.ts @@ -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); @@ -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) { @@ -262,6 +263,7 @@ export class FrameworkModule extends createModule({ return response; } } + this.addController(HttpRpcController); } diff --git a/packages/framework/src/worker.ts b/packages/framework/src/worker.ts index c1407ec63..94d74ed10 100644 --- a/packages/framework/src/worker.ts +++ b/packages/framework/src/worker.ts @@ -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'; @@ -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. @@ -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; @@ -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); @@ -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(); } @@ -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(); } } diff --git a/packages/http/src/module.config.ts b/packages/http/src/module.config.ts index 2972f9b5e..95008b85a 100644 --- a/packages/http/src/module.config.ts +++ b/packages/http/src/module.config.ts @@ -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; }