Merge pull request #437 from trails-cool/fix/planner-brouter-timeout
fix(planner/brouter): add 30s timeout to outbound fetch
This commit is contained in:
commit
d07207464e
3 changed files with 46 additions and 2 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { mergeGeoJsonSegments, type EnrichedRoute, type NoGoArea, type Waypoint } from "./route-merge";
|
||||
import { fetchWithTimeout } from "./http.server";
|
||||
|
||||
export { mergeGeoJsonSegments };
|
||||
export type { EnrichedRoute, NoGoArea };
|
||||
|
|
@ -63,7 +64,7 @@ export class BRouterError extends Error {
|
|||
}
|
||||
|
||||
async function fetchSegment(url: string): Promise<Record<string, unknown>> {
|
||||
const response = await fetch(url, { headers: authHeaders() });
|
||||
const response = await fetchWithTimeout(url, { headers: authHeaders() });
|
||||
if (!response.ok) {
|
||||
const body = await response.text();
|
||||
throw new BRouterError(body.trim(), response.status);
|
||||
|
|
@ -144,7 +145,7 @@ export async function computeSegmentGpx(request: {
|
|||
const nogoParam = request.noGoAreas?.length ? noGoAreasToParam(request.noGoAreas) : undefined;
|
||||
if (nogoParam) params.set("polygons", nogoParam);
|
||||
|
||||
const resp = await fetch(`${BROUTER_URL}/brouter?${params}`, {
|
||||
const resp = await fetchWithTimeout(`${BROUTER_URL}/brouter?${params}`, {
|
||||
headers: authHeaders(),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
|
|
|
|||
28
apps/planner/app/lib/http.server.test.ts
Normal file
28
apps/planner/app/lib/http.server.test.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { describe, it, expect, vi, afterEach } from "vitest";
|
||||
import { fetchWithTimeout } from "./http.server.ts";
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("fetchWithTimeout (planner)", () => {
|
||||
it("aborts when the upstream exceeds the timeout", async () => {
|
||||
globalThis.fetch = vi.fn((_input: RequestInfo | URL, init?: RequestInit) => {
|
||||
return new Promise((_resolve, reject) => {
|
||||
init?.signal?.addEventListener("abort", () => {
|
||||
reject(new DOMException("aborted", "AbortError"));
|
||||
});
|
||||
});
|
||||
}) as typeof fetch;
|
||||
await expect(fetchWithTimeout("https://example.test", {}, 25)).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("returns the response when the call resolves in time", async () => {
|
||||
globalThis.fetch = vi.fn(async () => new Response("ok", { status: 200 })) as typeof fetch;
|
||||
const resp = await fetchWithTimeout("https://example.test", {}, 1000);
|
||||
expect(resp.status).toBe(200);
|
||||
});
|
||||
});
|
||||
15
apps/planner/app/lib/http.server.ts
Normal file
15
apps/planner/app/lib/http.server.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Default timeout for outbound HTTP calls to third-party services
|
||||
// (BRouter, Overpass, …). A hung upstream must not stall the request
|
||||
// handler indefinitely.
|
||||
export const DEFAULT_EXTERNAL_FETCH_TIMEOUT_MS = 30_000;
|
||||
|
||||
export function fetchWithTimeout(
|
||||
input: RequestInfo | URL,
|
||||
init: RequestInit = {},
|
||||
timeoutMs: number = DEFAULT_EXTERNAL_FETCH_TIMEOUT_MS,
|
||||
): Promise<Response> {
|
||||
const signal = init.signal
|
||||
? AbortSignal.any([init.signal, AbortSignal.timeout(timeoutMs)])
|
||||
: AbortSignal.timeout(timeoutMs);
|
||||
return fetch(input, { ...init, signal });
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue