From CORS multi-permission errors to Cloudflare cache hit issue

一次完整的 Nginx + FastAPI TTS 服务排错记录

From CORS multi-permission errors to Cloudflare cache hit issue

最近为了保证Worder的英文朗读质量,我部署一个 TTS(Text-to-Speech)API接口微服务,并通过 Cloudflare 做加速缓存。一路踩坑的经历非常典型:

  • CORS 报错 MultipleAllowOriginValues
  • Cloudflare 免费套餐下,页面规则/缓存规则无法缓存 API → cf-cache-status: BYPASS
  • 本地缓存 x-cache: HIT,但 CDN 不命中
  • blob URL 出现 206 Partial Content

这篇文章记录完整排查过程和最终可行方案。


1. 背景

  • 前端站点:https://worder.me
  • TTS API:https://worder.me/api/tts?text=xxx&lang=en&format=mp3
  • 后端:Python FastAPI
  • 前置:Nginx 反向代理 + Cloudflare CDN

目标:让 TTS 结果在 Cloudflare 全站缓存(HIT),减轻服务器压力


2. CORS 报错 —— MultipleAllowOriginValues

前端播放音频报错:

Cross-Origin Resource Sharing error: MultipleAllowOriginValues

原因

  • FastAPI 添加了 CORS 中间件
  • Nginx 同时设置了 CORS 响应头

浏览器收到两个 Access-Control-Allow-Origin,报错。

解决

  • FastAPI 注释掉 CORS 中间件
  • Nginx 统一管理 CORS:
location /api/ {
    set $cors_origin "";
    if ($http_origin ~* ^https://(www\.)?worder\.me$) {
        set $cors_origin $http_origin;
    }
    add_header Access-Control-Allow-Origin $cors_origin always;
    add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
    add_header Access-Control-Allow-Headers "Content-Type" always;
}

✅ CORS 问题解决。


3. 免费套餐缓存规则 / 页面规则无法解决 BYPASS

尝试:

  • 页面规则:*worder.me/api/tts*
  • 缓存规则:URI 路径开头 /api/tts

但是 Cloudflare 免费套餐的 动态 API + query 参数 仍然显示:

cf-cache-status: BYPASS

原因:

  • 免费套餐无法自定义 Cache Key
  • Cloudflare 默认不缓存带 query 的动态请求
  • 页面规则 / 缓存规则只能缓存静态文件或完全匹配规则

所以必须使用 Worker 强制缓存


4. Worker 强制缓存解决方案

下面是我实际使用的 Worker 代码:

export default {
  async fetch(request) {
    const url = new URL(request.url);

    // 只处理 TTS API
    if (!url.pathname.startsWith('/api/tts')) {
      return fetch(request);
    }

    // 转发到源站
    const response = await fetch(url.toString(), {
      method: request.method,
      headers: request.headers,
      cf: {
        cacheEverything: true,      // ← 关键:强制 Cloudflare 缓存非静态内容
        cacheTtl: 2592000           // ← 缓存 30 天
      }
    });

    // 创建新响应,移除 Set-Cookie
    const newHeaders = new Headers(response.headers);
    newHeaders.delete('Set-Cookie');

    // 统一返回缓存头
    newHeaders.set('Cache-Control', 'public, max-age=2592000, immutable');

    return new Response(response.body, {
      status: response.status,
      headers: newHeaders
    });
  }
}

说明

  • cacheEverything: true → 即使是动态 API 也会缓存
  • cacheTtl: 2592000 → 缓存 30 天
  • 删除 Set-Cookie → 防止 Cloudflare 拒绝缓存
  • 返回 Cache-Control → 告诉浏览器和 CDN 这是可缓存内容

✅ 使用 Worker 后,cf-cache-status: HIT 正常命中。


5. 验证缓存效果

第一次请求:

cf-cache-status: MISS

第二次请求:

cf-cache-status: HIT 🎉

浏览器端播放正常,TTS 服务器压力大幅下降。


6. blob URL + 206 正常现象

浏览器将音频流转成 blob,206 Partial Content = 支持 Range 请求,可拖动播放,与缓存无关。


7. 总结关键点

  1. CORS 报错 → Nginx + FastAPI 双重设置,保留一个
  2. 免费套餐缓存规则/页面规则 → 动态 API 仍 BYPASS
  3. Worker 强制缓存cacheEverything + cacheTtl + Cache-Control
  4. 删除 Set-Cookie → 避免缓存被拒
  5. blob + 206 → 正常现象

🎉 最终效果

  • CORS OK
  • Cloudflare HIT 成功
  • TTS 服务压力降低
  • 全球 CDN 加速
  • 浏览器播放顺畅