UNPKG

@whatwg-node/server

Version:

Fetch API compliant HTTP Server adapter

60 lines (59 loc) 3.37 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useRequestDeadline = useRequestDeadline; const promise_helpers_1 = require("@whatwg-node/promise-helpers"); const abortSignalAny_js_1 = require("../abortSignalAny.js"); /** * A plugin that allows you to set a deadline for each request. If the request takes longer than * the specified timeout, the plugin will produce a response using the provided response factory function. * * This plugin is useful to prevent requests from taking too long and to provide a better experience * for the clients by sending a timely response when the server is under heavy load or when there are * long-running requests. * * This plugin also aborts the request signal when the deadline is reached. This means that if the * request handler, or any of the plugins hooks, are listening to the request signal, they will be able * to react to the deadline being reached and perform any necessary cleanup operations. */ function useRequestDeadline(opts) { return { onRequest({ request, requestHandler, setRequestHandler }) { // AbortSignal.timeout() creates an internal timer with no cancellation handle, so it keeps // the event loop alive and holds memory for the full timeout duration even when the request // finishes early. using AbortController + setTimeout gives us a timer handle we can clearTimeout const deadlineCtrl = new AbortController(); const deadlineSignal = deadlineCtrl.signal; const composedSignal = (0, abortSignalAny_js_1.abortSignalAny)([request.signal, deadlineSignal]); // overwrite the request signal with the composed signal. we intentionally // dont create a new request because that comes with a performance penalty Object.defineProperty(request, 'signal', { value: composedSignal }); setRequestHandler(function handlerWithDeadline(req, ctx) { if (deadlineSignal.aborted) { return opts.response(req, ctx); } return new Promise((resolve, reject) => { const timer = setTimeout(() => { deadlineCtrl.abort(); resolve(opts.response(req, ctx)); }, opts.timeout); function onDeadlineAbort() { clearTimeout(timer); resolve(opts.response(req, ctx)); } // handle the case where the deadline signal is aborted externally (e.g. via composedSignal) // before the timer fires - we still need to clear the timer deadlineSignal.addEventListener('abort', onDeadlineAbort, { once: true }); return (0, promise_helpers_1.handleMaybePromise)(() => requestHandler(req, ctx), result => { clearTimeout(timer); deadlineSignal.removeEventListener('abort', onDeadlineAbort); resolve(result); }, err => { clearTimeout(timer); deadlineSignal.removeEventListener('abort', onDeadlineAbort); reject(err); }); }); }); }, }; }