From 3bb3d4c82535e4c3bc946355967d40683a15fd1c Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 30 Jun 2024 16:20:47 -0300 Subject: fix double reading http response --- packages/taler-util/src/http-impl.node.ts | 2 +- packages/web-util/src/utils/http-impl.sw.ts | 126 +++++++++++++++++++--------- 2 files changed, 88 insertions(+), 40 deletions(-) diff --git a/packages/taler-util/src/http-impl.node.ts b/packages/taler-util/src/http-impl.node.ts index d27fd878d..fc5fe5e98 100644 --- a/packages/taler-util/src/http-impl.node.ts +++ b/packages/taler-util/src/http-impl.node.ts @@ -237,7 +237,7 @@ export class HttpLibImpl implements HttpRequestLibrary { }; doCleanup(); if (SHOW_CURL_HTTP_REQUEST) { - console.log(`TALER_API_DEBUG: ${textDecoder.decode(data)}`) + console.log(`TALER_API_DEBUG: ${res.statusCode} ${textDecoder.decode(data)}`) } resolve(resp); }); diff --git a/packages/web-util/src/utils/http-impl.sw.ts b/packages/web-util/src/utils/http-impl.sw.ts index 9c820bb4b..7b168b739 100644 --- a/packages/web-util/src/utils/http-impl.sw.ts +++ b/packages/web-util/src/utils/http-impl.sw.ts @@ -21,7 +21,7 @@ import { Duration, RequestThrottler, TalerError, - TalerErrorCode + TalerErrorCode, } from "@gnu-taler/taler-util"; import { @@ -85,7 +85,9 @@ export class BrowserFetchHttpLib implements HttpRequestLibrary { } const myBody: ArrayBuffer | undefined = - requestMethod === "POST" || requestMethod === "PUT" || requestMethod === "PATCH" + requestMethod === "POST" || + requestMethod === "PUT" || + requestMethod === "PATCH" ? encodeBody(requestBody) : undefined; @@ -93,8 +95,8 @@ export class BrowserFetchHttpLib implements HttpRequestLibrary { if (requestHeader) { Object.entries(requestHeader).forEach(([key, value]) => { if (value === undefined) return; - requestHeadersMap[key] = value - }) + requestHeadersMap[key] = value; + }); } const controller = new AbortController(); @@ -106,7 +108,7 @@ export class BrowserFetchHttpLib implements HttpRequestLibrary { } if (requestCancel) { requestCancel.onCancelled(() => { - controller.abort(TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR) + controller.abort(TalerErrorCode.GENERIC_CLIENT_INTERNAL_ERROR); }); } @@ -116,7 +118,7 @@ export class BrowserFetchHttpLib implements HttpRequestLibrary { body: myBody, method: requestMethod, signal: controller.signal, - redirect: requestRedirect + redirect: requestRedirect, }); if (timeoutId) { @@ -127,13 +129,15 @@ export class BrowserFetchHttpLib implements HttpRequestLibrary { response.headers.forEach((value, key) => { headerMap.set(key, value); }); + const text = makeTextHandler(response, requestUrl, requestMethod); + const json = makeJsonHandler(response, requestUrl, requestMethod, text); return { headers: headerMap, status: response.status, requestMethod, requestUrl, - json: makeJsonHandler(response, requestUrl, requestMethod), - text: makeTextHandler(response, requestUrl, requestMethod), + json, + text, bytes: async () => (await response.blob()).arrayBuffer(), }; } catch (e) { @@ -143,7 +147,8 @@ export class BrowserFetchHttpLib implements HttpRequestLibrary { { requestUrl, requestMethod, - timeoutMs: requestTimeout.d_ms === "forever" ? 0 : requestTimeout.d_ms + timeoutMs: + requestTimeout.d_ms === "forever" ? 0 : requestTimeout.d_ms, }, `HTTP request failed.`, ); @@ -151,7 +156,6 @@ export class BrowserFetchHttpLib implements HttpRequestLibrary { throw e; } } - } function makeTextHandler( @@ -159,20 +163,29 @@ function makeTextHandler( requestUrl: string, requestMethod: string, ) { - return async function getTextFromResponse(): Promise { - let respText; - try { - respText = await response.text(); - } catch (e) { - throw TalerError.fromDetail( - TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - { - requestUrl, - requestMethod, - httpStatusCode: response.status, - }, - "Invalid text from HTTP response", - ); + let firstTime = true; + let respText: string; + let error: TalerError | undefined; + return async function getTextFromResponse(): Promise { + if (firstTime) { + firstTime = false; + try { + respText = await response.text(); + } catch (e) { + error = TalerError.fromDetail( + TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, + { + requestUrl, + requestMethod, + httpStatusCode: response.status, + validationError: e instanceof Error ? e.message : String(e), + }, + "Invalid text from HTTP response", + ); + } + } + if (error !== undefined) { + throw error; } return respText; }; @@ -182,35 +195,70 @@ function makeJsonHandler( response: Response, requestUrl: string, requestMethod: string, + readTextHandler: () => Promise, ) { - let responseJson: unknown = undefined; + let firstTime = true; + let responseJson: string | undefined = undefined; + let error: TalerError | undefined; return async function getJsonFromResponse(): Promise { - if (responseJson === undefined) { + if (firstTime) { + let responseText: string; try { - responseJson = await response.json(); + responseText = await readTextHandler(); } catch (e) { - const message = e instanceof Error ? `Invalid JSON from HTTP response: ${e.message}` : "Invalid JSON from HTTP response" - throw TalerError.fromDetail( + const message = + e instanceof Error + ? `Couldn't read HTTP response: ${e.message}` + : "Couldn't read HTTP response"; + error = TalerError.fromDetail( TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, { requestUrl, requestMethod, httpStatusCode: response.status, + validationError: e instanceof Error ? e.message : String(e), }, message, ); } + if (!error) { + try { + // @ts-expect-error no error then text is initialized + responseJson = JSON.parse(responseText); + } catch (e) { + const message = + e instanceof Error + ? `Invalid JSON from HTTP response: ${e.message}` + : "Invalid JSON from HTTP response"; + error = TalerError.fromDetail( + TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, + { + requestUrl, + requestMethod, + // @ts-expect-error no error then text is initialized + response: responseText, + httpStatusCode: response.status, + validationError: e instanceof Error ? e.message : String(e), + }, + message, + ); + } + if (responseJson === null || typeof responseJson !== "object") { + error = TalerError.fromDetail( + TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, + { + requestUrl, + requestMethod, + response: JSON.stringify(responseJson), + httpStatusCode: response.status, + }, + "Invalid JSON from HTTP response: null or not object", + ); + } + } } - if (responseJson === null || typeof responseJson !== "object") { - throw TalerError.fromDetail( - TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE, - { - requestUrl, - requestMethod, - httpStatusCode: response.status, - }, - "Invalid JSON from HTTP response: null or not object", - ); + if (error !== undefined) { + throw error; } return responseJson; }; -- cgit v1.2.3