feat: timeout and retry fetches in proxy (#45)

* feat: timeout and retry fetches in proxy

* chore: fmt

* feat: move fetch changes to a shim in getFetchClient

* chore: fmt

* chore: remove unused import

* chore: fetch => globalThis.fetch

* chore: remove redundant use of async/await in promise-returning function

* feat: add initial configuration checks

* fix: use RequestInit directly

* fix: use networking subsection with kongfigStore

* chore: update fetch_timeout configuration value to fetch_timeout_ms
This commit is contained in:
Alex Maras 2025-02-19 23:26:10 +08:00 committed by GitHub
parent c0dafb1f7d
commit 3574d91d9f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 52 additions and 10 deletions

View file

@ -16,6 +16,11 @@ directory = "/var/tmp"
#proxy = ""
# Enable YouTube new video format UMP
ump = false
# fetch_timeout_ms = 10000
# fetch_retry_enable = true
# fetch_retry_times = 3
# fetch_retry_initial_debounce = 500
# fetch_retry_debounce_multiplier = 2
[jobs]

View file

@ -1,18 +1,21 @@
import { Store } from "@willsoto/node-konfig-core";
import { retry, type RetryOptions } from "jsr:@std/async";
type FetchInputParameter = Parameters<typeof fetch>[0];
type FetchInitParameterWithClient =
| RequestInit
| RequestInit & { client: Deno.HttpClient };
type FetchReturn = ReturnType<typeof fetch>;
export const getFetchClient = (konfigStore: Store): {
(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
(
input: Request | URL | string,
init?: RequestInit & {
client: Deno.HttpClient;
},
): Promise<Response>;
(input: URL | Request | string, init?: RequestInit): Promise<Response>;
input: FetchInputParameter,
init?: FetchInitParameterWithClient,
): FetchReturn;
} => {
if (Deno.env.get("PROXY") || konfigStore.get("networking.proxy")) {
return async (
input: RequestInfo | URL,
input: FetchInputParameter,
init?: RequestInit,
) => {
const client = Deno.createHttpClient({
@ -21,7 +24,7 @@ export const getFetchClient = (konfigStore: Store): {
konfigStore.get("networking.proxy") as string,
},
});
const fetchRes = await fetch(input, {
const fetchRes = await fetchShim(konfigStore, input, {
client,
headers: init?.headers,
method: init?.method,
@ -34,5 +37,39 @@ export const getFetchClient = (konfigStore: Store): {
};
}
return globalThis.fetch;
return (input: FetchInputParameter, init?: FetchInitParameterWithClient) =>
fetchShim(konfigStore, input, init);
};
function fetchShim(
konfigStore: Store,
input: FetchInputParameter,
init?: FetchInitParameterWithClient,
): FetchReturn {
const fetchTimeout = konfigStore.get("networking.fetch_timeout_ms");
const fetchRetry = konfigStore.get("networking.fetch_retry_enable");
const fetchMaxAttempts = konfigStore.get("networking.fetch_retry_times");
const fetchInitialDebounce = konfigStore.get(
"networking.fetch_retry_initial_debounce",
);
const fetchDebounceMultiplier = konfigStore.get(
"networking.fetch_retry_debounce_multiplier",
);
const retryOptions: RetryOptions = {
maxAttempts: Number(fetchMaxAttempts) || 1,
minTimeout: Number(fetchInitialDebounce) || 0,
multiplier: Number(fetchDebounceMultiplier) || 0,
jitter: 0,
};
const callFetch = () =>
fetch(input, {
// only set the AbortSignal if the timeout is supplied in the config
signal: fetchTimeout
? AbortSignal.timeout(Number(fetchTimeout))
: null,
...(init || {}),
});
// if retry enabled, call retry with the fetch shim, otherwise pass the fetch shim back directly
return fetchRetry ? retry(callFetch, retryOptions) : callFetch();
}