From c909d6fc0657002a2e5d117e98b9685f7a04a9d4 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Fri, 16 Feb 2024 00:30:25 +0100 Subject: taler-util: cancellation and timeouts on qjs --- packages/taler-util/src/http-impl.qtart.ts | 80 ++++++++++++++++++++++++++++-- packages/taler-util/src/index.ts | 1 + 2 files changed, 77 insertions(+), 4 deletions(-) (limited to 'packages/taler-util') diff --git a/packages/taler-util/src/http-impl.qtart.ts b/packages/taler-util/src/http-impl.qtart.ts index a37029d6e..0be9f2c23 100644 --- a/packages/taler-util/src/http-impl.qtart.ts +++ b/packages/taler-util/src/http-impl.qtart.ts @@ -19,9 +19,9 @@ /** * Imports. */ -import { Logger } from "@gnu-taler/taler-util"; +import { Logger, openPromise } from "@gnu-taler/taler-util"; import { TalerError } from "./errors.js"; -import { encodeBody, getDefaultHeaders, HttpLibArgs } from "./http-common.js"; +import { HttpLibArgs, encodeBody, getDefaultHeaders } from "./http-common.js"; import { Headers, HttpRequestLibrary, @@ -29,12 +29,26 @@ import { HttpResponse, } from "./http.js"; import { RequestThrottler, TalerErrorCode, URL } from "./index.js"; -import { qjsOs } from "./qtart.js"; +import { QjsHttpResp, qjsOs } from "./qtart.js"; const logger = new Logger("http-impl.qtart.ts"); const textDecoder = new TextDecoder(); +export class RequestTimeoutError extends Error { + public constructor() { + super("Request timed out"); + Object.setPrototypeOf(this, RequestTimeoutError.prototype); + } +} + +export class RequestCancelledError extends Error { + public constructor() { + super("Request cancelled"); + Object.setPrototypeOf(this, RequestCancelledError.prototype); + } +} + /** * Implementation of the HTTP request library interface for node. */ @@ -92,12 +106,70 @@ export class HttpLibImpl implements HttpRequestLibrary { if (method === "POST") { data = encodeBody(opt?.body); } - const res = await qjsOs.fetchHttp(url, { + + const cancelPromCap = openPromise(); + + // Just like WHATWG fetch(), the qjs http client doesn't + // really support cancellation, so cancellation here just + // means that the result is ignored! + const fetchProm = qjsOs.fetchHttp(url, { method, data, headers: headersList, }); + let timeoutHandle: any = undefined; + let cancelCancelledHandler: (() => void) | undefined = undefined; + + if (opt?.timeout && opt.timeout.d_ms !== "forever") { + timeoutHandle = setTimeout(() => { + cancelPromCap.reject(new RequestTimeoutError()); + }, opt.timeout.d_ms); + } + + if (opt?.cancellationToken) { + cancelCancelledHandler = opt.cancellationToken.onCancelled(() => { + cancelPromCap.reject(new RequestCancelledError()); + }); + } + + let res: QjsHttpResp; + try { + res = await Promise.race([fetchProm, cancelPromCap.promise]); + } catch (e) { + if (e instanceof RequestCancelledError) { + throw TalerError.fromDetail( + TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, + { + requestUrl: url, + requestMethod: method, + httpStatusCode: 0, + }, + `Request cancelled`, + ); + } + if (e instanceof RequestTimeoutError) { + throw TalerError.fromDetail( + TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR, + { + requestUrl: url, + requestMethod: method, + httpStatusCode: 0, + }, + `Request timed out`, + ); + } + throw e; + } + + if (timeoutHandle != null) { + clearTimeout(timeoutHandle); + } + + if (cancelCancelledHandler != null) { + cancelCancelledHandler(); + } + const headers: Headers = new Headers(); if (res.headers) { diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts index 2045a4717..edc9c4ff2 100644 --- a/packages/taler-util/src/index.ts +++ b/packages/taler-util/src/index.ts @@ -44,6 +44,7 @@ export { export * from "./notifications.js"; export * from "./operation.js"; export * from "./payto.js"; +export * from "./promises.js"; export * from "./rfc3548.js"; export * from "./taler-crypto.js"; export * from "./taler-types.js"; -- cgit v1.2.3