Skip to content

Commit cc7e71e

Browse files
authored
Merge pull request #431 from podium-lib/cherry_pick_timeout_fix
Cherry pick timeout fix
2 parents 1045c21 + ebdfc74 commit cc7e71e

File tree

3 files changed

+69
-8
lines changed

3 files changed

+69
-8
lines changed

lib/http.js

+23-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { request } from 'undici';
1+
import { request as undiciRequest } from 'undici';
22

33
/**
44
* @typedef {object} PodiumHttpClientRequestOptions
@@ -7,22 +7,38 @@ import { request } from 'undici';
77
* @property {boolean} [rejectUnauthorized]
88
* @property {boolean} [follow]
99
* @property {number} [timeout]
10-
* @property {number} [bodyTimeout]
1110
* @property {object} [query]
1211
* @property {import('http').IncomingHttpHeaders} [headers]
1312
*/
1413

1514
export default class HTTP {
15+
constructor(requestFn = undiciRequest) {
16+
this.requestFn = requestFn;
17+
}
18+
1619
/**
1720
* @param {string} url
1821
* @param {PodiumHttpClientRequestOptions} options
1922
* @returns {Promise<Pick<import('undici').Dispatcher.ResponseData, 'statusCode' | 'headers' | 'body'>>}
2023
*/
2124
async request(url, options) {
22-
const { statusCode, headers, body } = await request(
23-
new URL(url),
24-
options,
25-
);
26-
return { statusCode, headers, body };
25+
const abortController = new AbortController();
26+
27+
const timeoutId = setTimeout(() => {
28+
abortController.abort();
29+
}, options.timeout || 1000);
30+
31+
try {
32+
const { statusCode, headers, body } = await this.requestFn(
33+
new URL(url),
34+
{
35+
...options,
36+
signal: abortController.signal,
37+
},
38+
);
39+
return { statusCode, headers, body };
40+
} finally {
41+
clearTimeout(timeoutId);
42+
}
2743
}
2844
}

lib/resolver.content.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ export default class PodletClientContentResolver {
136136
/** @type {import('./http.js').PodiumHttpClientRequestOptions} */
137137
const reqOptions = {
138138
rejectUnauthorized: outgoing.rejectUnauthorized,
139-
bodyTimeout: outgoing.timeout,
139+
timeout: outgoing.timeout,
140140
method: 'GET',
141141
query: outgoing.reqOptions.query,
142142
headers,
@@ -267,6 +267,7 @@ export default class PodletClientContentResolver {
267267
}),
268268
);
269269

270+
// @ts-ignore
270271
pipeline([body, outgoing], (err) => {
271272
if (err) {
272273
this.#log.warn('error while piping content stream', err);

tests/http.test.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { test } from 'node:test';
2+
import { rejects } from 'node:assert';
3+
import HTTP from '../lib/http.js';
4+
5+
test('should abort the request if it takes longer than the timeout', async () => {
6+
// Mock the undici.Client's request method
7+
const mockRequestFn = async (url, { signal }) => {
8+
return new Promise((resolve, reject) => {
9+
// Simulate a delay longer than the timeout
10+
setTimeout(() => {
11+
if (signal.aborted) {
12+
const abortError = new Error(
13+
'Request aborted due to timeout',
14+
);
15+
abortError.name = 'AbortError';
16+
reject(abortError);
17+
} else {
18+
resolve({
19+
statusCode: 200,
20+
headers: {},
21+
body: 'OK',
22+
});
23+
}
24+
}, 2000); // 2 seconds delay
25+
});
26+
};
27+
28+
// @ts-ignore
29+
const http = new HTTP(mockRequestFn);
30+
const url = 'https://example.com/test';
31+
const options = {
32+
method: /** @type {'GET'} */ ('GET'),
33+
timeout: 1000, // 1 second timeout
34+
};
35+
36+
// Assert that the request is rejected with an AbortError
37+
await rejects(
38+
http.request(url, options),
39+
(/** @type {Error} */ err) =>
40+
err.name === 'AbortError' &&
41+
err.message === 'Request aborted due to timeout',
42+
'Expected request to be aborted due to timeout',
43+
);
44+
});

0 commit comments

Comments
 (0)