← [ BACK_TO_NOTES ]

限流响应头

限流响应头(供应商 X-RateLimit-*、旧版 RateLimit-*、当前草案 RateLimit / RateLimit-Policy 与 Retry-After)告诉客户端当前的限流状态、剩余配额和重试时机。

#concept #growing #backend #http #http

[!info] related notes

限流响应头

一句话定义

限流响应头告诉客户端:你的请求配额是多少、还剩多少、什么时候重试。前端或 SDK 根据这些头做退避、提示和重试。

它要解决什么问题

API 限流是保护后端服务的基本手段。429 表示请求受到限流;服务可额外提供以下信息,帮助客户端决定是否及何时重试:

  • 我还能发多少请求?(限流上限)
  • 我还剩多少配额?(剩余次数)
  • 我什么时候可以重试?(重试时机)

字段家族与版本

X-RateLimit-*(非标准但广泛使用)

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1716540000
头含义
X-RateLimit-Limit时间窗口内的总配额
X-RateLimit-Remaining当前窗口内剩余配额
X-RateLimit-Reset此类示例常用 Unix 秒时间戳;单位必须查供应商合同

RateLimit-*(旧草案与兼容实现)

RateLimit-Limit: 1000
RateLimit-Remaining: 42
RateLimit-Reset: 60
头含义
RateLimit-Limit时间窗口内的总配额
RateLimit-Remaining当前窗口内剩余配额
RateLimit-Reset配额重置前的秒数(不是时间戳)

上述三字段写法属于旧版草案/兼容实现:示例 RateLimit-Reset 用相对秒数,X-RateLimit-Reset 的单位由供应商定义。不能仅凭字段名假定所有服务都遵守同一含义。

RateLimit / RateLimit-Policy(当前草案)

截至 2026-10-06,IETF Datatracker 最新为 draft-ietf-httpapi-ratelimit-headers-11,仍是 Internet-Draft,尚非 RFC。它采用结构化字段:

RateLimit-Policy: "default";q=1000;w=3600
RateLimit: "default";r=42;t=60

q/w 描述配额策略及窗口;第 11 版的 r 是可用配额,t 是有效窗口(秒),表示该窗口内不能使用超过 r 的配额,不是到点保证全部重置。配额单位也不一定等于请求次数。真实客户端应按服务采用的版本解析,不能用 parseInt 读取整个结构化字段;配额大于零也不保证下一请求成功。

Retry-After

RFC 9110 定义 Retry-After 为非负整数秒数或 HTTP 日期;常用于 429/503,也可用于重定向响应。它建议等待时长,不预留配额或保证重试成功。

HTTP/1.1 429 Too Many Requests
Retry-After: 60

或用 HTTP 日期格式:

HTTP/1.1 503 Service Unavailable
Retry-After: Sun, 24 May 2026 10:30:00 GMT

常见搭配

# 限流
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1716540000

# 服务不可用
HTTP/1.1 503 Service Unavailable
Retry-After: 120

前端如何使用

退避重试

下面只自动重试 GET 的 429;maxAttempts 包含首次请求。没有可用提示时指数退避并加抖动;服务器要求等待超过本例 30 秒预算时直接交还响应,不提前重试。写操作需单独设计幂等与请求体重放,不能直接套用。

function retryAfterMs(value, now = Date.now()) {
  if (value == null) return null;
  const text = value.trim();
  if (/^\d+$/.test(text)) {
    const ms = Number(text) * 1000;
    return Number.isSafeInteger(ms) ? ms : Infinity;
  }
  // HTTP 日期以星期名开头;避免 Date.parse 把负数等当成日期。
  if (!/^[A-Za-z]{3,9}(?:,| )/.test(text)) return null;
  const date = Date.parse(text);
  return Number.isFinite(date) ? Math.max(0, date - now) : null;
}

function wait(ms, signal) {
  return new Promise((resolve, reject) => {
    if (signal?.aborted) return reject(signal.reason);
    const onAbort = () => {
      clearTimeout(timer);
      reject(signal.reason);
    };
    const timer = setTimeout(() => {
      signal?.removeEventListener("abort", onAbort);
      resolve();
    }, ms);
    signal?.addEventListener("abort", onAbort, { once: true });
  });
}

async function fetchGetWithRetry(url, { signal, maxAttempts = 3 } = {}) {
  if (!Number.isInteger(maxAttempts) || maxAttempts < 1 || maxAttempts > 10) {
    throw new RangeError("maxAttempts must be 1..10");
  }
  const started = Date.now();
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    const res = await fetch(url, { method: "GET", signal });
    if (res.status !== 429 || attempt === maxAttempts - 1) return res;
    const advised = retryAfterMs(res.headers.get("Retry-After"));
    const delay = (advised ?? 1000 * 2 ** attempt) + Math.random() * 250;
    if (delay > 30_000 - (Date.now() - started)) return res;
    // 丢弃的响应释放 body;最后交还的响应留给调用方读取。
    await res.body?.cancel();
    await wait(delay, signal);
  }
}

日期计算依赖客户端时钟;这里用 Date.parse 演示浏览器日期解析,并非严格 HTTP-date 校验器。30 秒仅限制继续退避的预算,不能中断已开始的 fetch;调用方应传入带整体 deadline 的 AbortSignal。网络错误会直接抛出,最后一次 429 会原样返回,调用方仍须检查 status。

显示剩余配额

const res = await fetch("/api/data");
const remaining = res.headers.get("X-RateLimit-Remaining");

if (remaining !== null && /^\d+$/.test(remaining) && Number(remaining) < 10) {
  showToast("API 调用次数即将用完,请稍后再试");
}

跨域场景

这些字段不在 CORS 安全列出的响应头集合中,包括标准 Retry-After;跨域 JS 读取需服务端显式暴露实际使用的字段:

Access-Control-Expose-Headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, RateLimit, RateLimit-Policy, Retry-After

否则:

res.headers.get("X-RateLimit-Remaining") // null

边界与易混淆点

  • X-RateLimit- 不是 RFC 标准*:是约定俗成的实践。不同 API 提供商的实现可能有差异。
  • 草案必须标版本:旧 RateLimit-* 与第 11 版 RateLimit / RateLimit-Policy 不可混为一套固定协议。
  • Retry-After 的值是秒数或 HTTP 日期:不是毫秒,也不是 Unix 时间戳;X-RateLimit-Reset 是另一个字段。
  • 429 不一定带限流头:有些 API 只返回 429 状态码,不提供详细限流信息。这时客户端可按合同选择退避或停止重试,不能无限重试。
  • 限流窗口设计影响用户体验:滑动窗口比固定窗口更平滑,但实现更复杂。

依据

创建于 2026/5/24 更新于 2026/10/6
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本