-
-
Notifications
You must be signed in to change notification settings - Fork 199
fix(proxy): 检测 200+HTML 的假200并触发故障转移(#749) #763
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
a374d47
fix(proxy): 识别 200+HTML 的假200并触发故障转移(#749)
tesgth032 e315a62
fix(proxy): 限制非流式响应体嗅探大小,避免内存占用失控
tesgth032 c9bafaf
fix(proxy): 修复空响应检测被 JSON 解析 catch 吞掉
tesgth032 3fadacd
test(proxy): 覆盖 missing_content 触发切换场景
tesgth032 dd5da16
chore: format code (fix-fake-200-html-issue-749-3fadacd)
github-actions[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ import { parseSSEData } from "@/lib/utils/sse"; | |
| * | ||
| * 设计目标(偏保守) | ||
| * - 仅基于结构化字段做启发式判断:`error` 与 `message`; | ||
| * - 对明显的 HTML 文档(doctype/html 标签)做强信号判定,覆盖部分网关/WAF/Cloudflare 返回的“假 200”; | ||
| * - 不扫描模型生成的正文内容(例如 content/choices),避免把用户/模型自然语言里的 "error" 误判为上游错误; | ||
| * - message 关键字检测仅对“小体积 JSON”启用,降低误判与性能开销。 | ||
| * - 返回的 `code` 是语言无关的错误码(便于写入 DB/监控/告警); | ||
|
|
@@ -53,6 +54,7 @@ const DEFAULT_MESSAGE_KEYWORD = /error/i; | |
|
|
||
| const FAKE_200_CODES = { | ||
| EMPTY_BODY: "FAKE_200_EMPTY_BODY", | ||
| HTML_BODY: "FAKE_200_HTML_BODY", | ||
| JSON_ERROR_NON_EMPTY: "FAKE_200_JSON_ERROR_NON_EMPTY", | ||
| JSON_ERROR_MESSAGE_NON_EMPTY: "FAKE_200_JSON_ERROR_MESSAGE_NON_EMPTY", | ||
| JSON_MESSAGE_KEYWORD_MATCH: "FAKE_200_JSON_MESSAGE_KEYWORD_MATCH", | ||
|
|
@@ -63,6 +65,16 @@ const FAKE_200_CODES = { | |
| const MAY_HAVE_JSON_ERROR_KEY = /"error"\s*:/; | ||
| const MAY_HAVE_JSON_MESSAGE_KEY = /"message"\s*:/; | ||
|
|
||
| const HTML_DOC_SNIFF_MAX_CHARS = 1024; | ||
| const HTML_DOCTYPE_RE = /^<!doctype\s+html[\s>]/i; | ||
| const HTML_HTML_TAG_RE = /<html[\s>]/i; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| function isLikelyHtmlDocument(trimmedText: string): boolean { | ||
| if (!trimmedText.startsWith("<")) return false; | ||
| const head = trimmedText.slice(0, HTML_DOC_SNIFF_MAX_CHARS); | ||
| return HTML_DOCTYPE_RE.test(head) || HTML_HTML_TAG_RE.test(head); | ||
| } | ||
|
|
||
| function isPlainRecord(value: unknown): value is Record<string, unknown> { | ||
| return !!value && typeof value === "object" && !Array.isArray(value); | ||
| } | ||
|
|
@@ -194,6 +206,20 @@ export function detectUpstreamErrorFromSseOrJsonText( | |
| return { isError: true, code: FAKE_200_CODES.EMPTY_BODY }; | ||
| } | ||
|
|
||
| // 情况 0:明显的 HTML 文档(通常是网关/WAF/Cloudflare 返回的错误页) | ||
| // | ||
| // 说明: | ||
| // - 此处不依赖 Content-Type:部分上游会缺失/错误设置该字段; | ||
| // - 仅匹配 doctype/html 标签等“强信号”,避免把普通 `<...>` 文本误判为 HTML 页面。 | ||
| if (isLikelyHtmlDocument(trimmed)) { | ||
| return { | ||
| isError: true, | ||
| code: FAKE_200_CODES.HTML_BODY, | ||
| // 避免对超大 HTML 做无谓处理:仅截取前段用于脱敏/截断与排查 | ||
| detail: truncateForDetail(trimmed.slice(0, 4096)), | ||
| }; | ||
| } | ||
|
|
||
| // 情况 1:纯 JSON(对象) | ||
| // 上游可能 Content-Type 设置为 SSE,但实际上返回 JSON;此处只处理对象格式({...}), | ||
| // 不处理数组([...])以避免误判(数组场景的语义差异较大,后续若确认需要再扩展)。 | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added
readResponseTextUpTofunction to safely read response bodies with a 1MB limit, preventing memory exhaustion from malicious or misconfigured upstream providers.Prompt To Fix With AI