parent
ea424b1b3b
commit
2aeef3010e
@ -0,0 +1,93 @@
|
||||
import { mergeHeaders } from './body.ts';
|
||||
import {
|
||||
HTTP_DIAGNOSTIC_EVENTS,
|
||||
HTTP_HEADER_CONTENT_TYPE
|
||||
} from './consts.ts';
|
||||
import { emitHttpDiagnostic } from './diagnostics.ts';
|
||||
import { runBeforeRequest } from './hooks.ts';
|
||||
import { classifyAbort } from './timeout.ts';
|
||||
import type { ResolvedHttpDefaults } from './engine-options.ts';
|
||||
import type {
|
||||
HookContext,
|
||||
HookRequest,
|
||||
HttpHooks,
|
||||
HttpInit,
|
||||
HttpMethod
|
||||
} from './types.ts';
|
||||
|
||||
export interface HttpAttemptInput {
|
||||
readonly defaults: ResolvedHttpDefaults;
|
||||
readonly hooks: Required<HttpHooks>;
|
||||
readonly fetchImpl: typeof fetch;
|
||||
readonly method: HttpMethod;
|
||||
readonly fullUrl: string;
|
||||
readonly init: HttpInit | undefined;
|
||||
readonly attempt: number;
|
||||
readonly body: BodyInit | undefined;
|
||||
readonly contentType: string | undefined;
|
||||
readonly signal: AbortSignal;
|
||||
}
|
||||
|
||||
export interface HttpAttemptResult {
|
||||
readonly ctx: HookContext;
|
||||
readonly response: Response | undefined;
|
||||
readonly error: unknown;
|
||||
}
|
||||
|
||||
export async function runHttpAttempt(input: HttpAttemptInput): Promise<HttpAttemptResult> {
|
||||
const headers = await mergeHeaders(input.defaults.headers, input.init?.headers);
|
||||
if (input.contentType !== undefined && !headers.has(HTTP_HEADER_CONTENT_TYPE)) {
|
||||
headers.set(HTTP_HEADER_CONTENT_TYPE, input.contentType);
|
||||
}
|
||||
|
||||
const request: HookRequest = {
|
||||
method: input.method,
|
||||
headers,
|
||||
body: input.body,
|
||||
signal: input.signal
|
||||
};
|
||||
const ctx: HookContext = {
|
||||
url: input.fullUrl,
|
||||
request,
|
||||
attempt: input.attempt,
|
||||
init: input.init ?? {}
|
||||
};
|
||||
|
||||
const earlyResponse = await runBeforeRequest(input.hooks.beforeRequest, ctx);
|
||||
|
||||
try {
|
||||
emitHttpDiagnostic(input.defaults.diagnostics, HTTP_DIAGNOSTIC_EVENTS.REQUEST, {
|
||||
method: input.method,
|
||||
url: ctx.url,
|
||||
attempt: input.attempt
|
||||
});
|
||||
|
||||
const response =
|
||||
earlyResponse instanceof Response
|
||||
? earlyResponse
|
||||
: await input.fetchImpl(ctx.url, {
|
||||
method: ctx.request.method,
|
||||
headers: ctx.request.headers,
|
||||
body: ctx.request.body,
|
||||
signal: ctx.request.signal
|
||||
});
|
||||
return { ctx, response, error: undefined };
|
||||
} catch (err) {
|
||||
const error = classifyFetchError(err, input.signal);
|
||||
emitHttpDiagnostic(input.defaults.diagnostics, HTTP_DIAGNOSTIC_EVENTS.NETWORK_ERROR, {
|
||||
method: input.method,
|
||||
url: ctx.url,
|
||||
attempt: input.attempt,
|
||||
error: error instanceof Error ? error : new Error(String(error))
|
||||
});
|
||||
return { ctx, response: undefined, error };
|
||||
}
|
||||
}
|
||||
|
||||
function classifyFetchError(err: unknown, signal: AbortSignal): unknown {
|
||||
if (signal.aborted) {
|
||||
const classified = classifyAbort(signal);
|
||||
if (classified !== null) return classified;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
Loading…
Reference in new issue