From 3991c9b4bbcd3039bd27eb5352cddd76a6a866b8 Mon Sep 17 00:00:00 2001 From: garrettmills Date: Sat, 1 Aug 2020 13:59:38 -0500 Subject: [PATCH] Add support for request timeouts; timeout helper --- TODO.txt | 4 +-- app/configs/server.config.ts | 2 ++ lib/src/support/timeout.ts | 48 ++++++++++++++++++++++++++++++++++++ lib/src/unit/HttpServer.ts | 23 +++++++++++++++-- 4 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 lib/src/support/timeout.ts diff --git a/TODO.txt b/TODO.txt index 5bdecb9..264c4c6 100644 --- a/TODO.txt +++ b/TODO.txt @@ -22,6 +22,4 @@ unit tests integration tests documentation & docs site events and observables? -no matching route handler -request timeout -error response handler figure out json +error response handler figure out json - requested content type diff --git a/app/configs/server.config.ts b/app/configs/server.config.ts index b86707b..90f8ba1 100644 --- a/app/configs/server.config.ts +++ b/app/configs/server.config.ts @@ -4,6 +4,8 @@ export default { prefix: '/', allow_mount_without_prefix: false, + request_timeout: 15000, // 15 seconds + powered_by: { enable: true, text: 'Daton', diff --git a/lib/src/support/timeout.ts b/lib/src/support/timeout.ts new file mode 100644 index 0000000..c1e71d3 --- /dev/null +++ b/lib/src/support/timeout.ts @@ -0,0 +1,48 @@ +export interface TimeoutSubscriber { + on_time: (handler: (arg: T) => any) => TimeoutSubscriber, + late: (handler: (arg: T) => any) => TimeoutSubscriber, + timeout: (handler: () => any) => TimeoutSubscriber, + run: () => Promise, +} + +export function withTimeout(timeout: number, promise: Promise) { + let on_time_handler: (arg: T) => any = (arg) => {} + let late_handler: (arg: T) => any = (arg) => {} + let timeout_handler: () => any = () => {} + + const sub = { + on_time: handler => { + on_time_handler = handler + return sub + }, + late: handler => { + late_handler = handler + return sub + }, + timeout: handler => { + timeout_handler = handler + return sub + }, + run: async () => { + let expired = false + let resolved = false + setTimeout(() => { + expired = true + if ( !resolved ) timeout_handler() + }, timeout) + + const result: T = await promise + resolved = true + + if ( !expired ) { + await on_time_handler(result) + } else { + await late_handler(result) + } + + return result + } + } as TimeoutSubscriber + + return sub +} \ No newline at end of file diff --git a/lib/src/unit/HttpServer.ts b/lib/src/unit/HttpServer.ts index 91984fb..1f7dc42 100644 --- a/lib/src/unit/HttpServer.ts +++ b/lib/src/unit/HttpServer.ts @@ -4,6 +4,10 @@ import Kernel from '../http/kernel/Kernel.ts' import {Logging} from '../service/logging/Logging.ts' import {serve} from '../external/http.ts' import {Request} from '../http/Request.ts' +import {withTimeout} from '../support/timeout.ts' +import {http} from '../http/response/helpers.ts' +import {HTTPStatus} from '../const/http.ts' +import Config from './Config.ts' @Unit() export default class HttpServer extends LifecycleUnit { @@ -11,6 +15,7 @@ export default class HttpServer extends LifecycleUnit { constructor( protected readonly kernel: Kernel, + protected readonly config: Config, protected readonly logger: Logging, ) { super() @@ -20,10 +25,24 @@ export default class HttpServer extends LifecycleUnit { this._server = serve({ port: 8000 }) this.logger.success(`HTTP/S server listening on port 8000!`) + const request_timeout: number = this.config.get('server.request_timeout', 15000) + for await ( const native_request of this._server ) { let req: Request = this.make(Request, native_request) - req = await this.kernel.handle(req) - req.response.send() + + await withTimeout(request_timeout, this.kernel.handle(req)) + .on_time(output_req => { + output_req.response.send() + }) + .late(output_req => { + this.logger.error(`Request timed out. Response was already sent on path: ${req.path}. Consider increasing server.request_timeout.`) + }) + .timeout(async () => { + const factory = http(HTTPStatus.REQUEST_TIMEOUT) + const output_req = await factory.write(req) + output_req.response.send() + }) + .run() } } }