Files
group_fqcd_jr/app/api/dependencies/negotiation.py
T

48 lines
2.0 KiB
Python
Raw Normal View History

"""SSE 内容协商(`docs/05` §3.2、§3.5)。
`Accept` 在文档里是**非必填**头,所以判定规则是"客户端是否**显式拒绝**了
`text/event-stream`",而不是"客户端是否显式接受"。
放在这里而不是写在某个 Controller 里,是因为平台有两个 SSE 端点
(`/api/v1/agent-runs/{run_id}/events` 与 `/api/v1/risk/daily-report/stream`):口径
一旦分叉,同一份客户端代码就会在一个端点上拿到 200、在另一个端点上拿到 406。
"""
from __future__ import annotations
SSE_MEDIA_TYPE = "text/event-stream"
# `text/*` 与 `*/*` 都覆盖 `text/event-stream`,属于"可接受"。
ACCEPTABLE_SSE_TYPES = frozenset({SSE_MEDIA_TYPE, "text/*", "*/*"})
def accepts_event_stream(accept: str | None) -> bool:
"""`Accept` 是否接受 `text/event-stream`(文档 §3.2:该头**非必填**)。
- 未携带(`None` 或空串)→ 放行:文档写明"默认 `application/json`;SSE 为
`text/event-stream`",即由接口自身决定响应类型,不是客户端错误;
- 携带 `text/event-stream`、`text/*` 或 `*/*` 且 `q != 0` → 放行;
- 显式携带但只接受其他类型(如 `application/json`)→ 拒绝,由调用方转
`406 SSE_NOT_ACCEPTABLE`(文档 §3.5/§6.4)。
只做"是否可接受"的判定,不参与内容协商排序:SSE 端点只有一种表示。
"""
if accept is None or not accept.strip():
return True
for entry in accept.split(","):
parts = entry.split(";")
media_type = parts[0].strip().lower()
if media_type not in ACCEPTABLE_SSE_TYPES:
continue
quality = 1.0
for parameter in parts[1:]:
name, _, value = parameter.partition("=")
if name.strip().lower() == "q":
try:
quality = float(value.strip())
except ValueError:
quality = 0.0
if quality > 0:
return True
return False