1
0
Files
DiAL/src/server/checker/fetcher.ts
lanyuanxiaoyao f7facb7232 refactor: 全面优化后端代码质量与架构
- app.ts 单体路由拆分为 routes/ + helpers + middleware + static 独立模块
- 类型去重:CheckFailure/CheckResult 以 shared/api.ts 为唯一源头,收紧 phase 联合类型
- es-toolkit 替换:isPlainObject/isNil/isEmptyObject/isEqual/isError/Semaphore/groupBy
- Bun 内置 API:Object.fromEntries 替代手写 headersToRecord
- bun:sqlite 规范:prepare() → query() 利用内置缓存,避免 N+1 查询
- 新增 getLatestChecksMap/allGetTargetStats 批量查询方法
- 新增 backend-code-quality/api-route-separation/batch-data-queries 规范
- 补充 openspec/config.yaml 后端开发规范与 DEVELOPMENT.md 后端开发指引
2026-05-12 15:15:36 +08:00

94 lines
3.0 KiB
TypeScript

import type { CheckResult, ResolvedHttpTarget } from "./types";
import { checkHttpExpect } from "./expect/http";
import { errorFailure } from "./expect/failure";
import { isError } from "es-toolkit";
export async function runHttpCheck(target: ResolvedHttpTarget): Promise<CheckResult> {
const timestamp = new Date().toISOString();
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), target.timeoutMs);
try {
const start = performance.now();
const response = await fetch(target.http.url, {
method: target.http.method,
headers: target.http.headers,
body: target.http.method !== "GET" && target.http.method !== "HEAD" ? target.http.body : undefined,
signal: controller.signal,
});
const durationMs = Math.round(performance.now() - start);
const statusCode = response.status;
const responseHeaders = Object.fromEntries(response.headers);
const hasBodyRules = !!(target.expect?.body && target.expect.body.length > 0);
const preBodyExpect = target.expect
? { status: target.expect.status, maxDurationMs: target.expect.maxDurationMs, headers: target.expect.headers }
: undefined;
const preBodyObs = { statusCode, headers: responseHeaders, body: null as string | null, durationMs };
const preBodyResult = checkHttpExpect(preBodyObs, preBodyExpect);
if (!hasBodyRules || !preBodyResult.matched) {
clearTimeout(timeoutId);
return {
targetName: target.name,
timestamp,
matched: preBodyResult.matched,
durationMs,
statusDetail: `HTTP ${statusCode}`,
failure: preBodyResult.failure,
};
}
const bodyBuffer = await response.arrayBuffer();
clearTimeout(timeoutId);
if (bodyBuffer.byteLength > target.http.maxBodyBytes) {
return {
targetName: target.name,
timestamp,
matched: false,
durationMs,
statusDetail: `HTTP ${statusCode}`,
failure: errorFailure(
"body",
"body",
`响应体大小 ${bodyBuffer.byteLength} 超过限制 ${target.http.maxBodyBytes}`,
),
};
}
const body = new TextDecoder().decode(bodyBuffer);
const fullObs = { statusCode, headers: responseHeaders, body, durationMs };
const fullResult = checkHttpExpect(fullObs, target.expect);
return {
targetName: target.name,
timestamp,
matched: fullResult.matched,
durationMs,
statusDetail: `HTTP ${statusCode}`,
failure: fullResult.failure,
};
} catch (error) {
clearTimeout(timeoutId);
const isTimeout = error instanceof DOMException && error.name === "AbortError";
return {
targetName: target.name,
timestamp,
matched: false,
durationMs: null,
statusDetail: null,
failure: errorFailure(
"status",
"request",
isTimeout ? `请求超时 (${target.timeoutMs}ms)` : isError(error) ? error.message : String(error),
),
};
}
}