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 加速
  • 浏览器播放顺畅

Read more

城乡差距背后的高墙

城乡差距背后的高墙

2024年的官方数据显示,中国城镇化率已达67%,城乡收入比缩小至2.34。这些数字看起来令人鼓舞——我们似乎正稳步迈向城乡融合的理想图景。 但真相往往藏在数字的褶皱里。 当我深入阅读这份城乡差距研究报告时,一个令人不安的发现浮出水面:表面上缩小的"硬差距"背后,是愈发固化的"软差距",以及不断涌现的新型鸿沟。更关键的是,我们需要对这些官方数据保持必要的审慎——毕竟,统计口径的选择、样本的代表性、以及数据采集的真实性,都可能影响我们对现实的判断。 一、收入的悖论:相对缩小与绝对扩大 表象:城乡收入比在下降 报告显示,2024年农村居民收入增速(6.6%)快于城镇(4.6%),推动城乡收入比从2.39降至2.34。这符合"共同富裕"的政策叙事。 真相:绝对差距突破3万元 但如果我们看绝对金额,会发现城镇居民人均可支配收入54,

By 王圆圆
闭源的中医

闭源的中医

当我们谈论中医和西医的差异时,很容易陷入"传统与现代"、"整体与局部"这类老生常谈的对比。但如果换一个角度——会发现一个反直觉的真相:看似神秘、强调个人经验的中医,实际上更像一个"闭源系统";而标准化、机械化的西医,反而是真正的"开源"。 这不仅仅是个有趣的比喻。这种知识传承方式的根本差异,决定了两套医学体系的进化路径,也解释了为什么当代中国出现了一个吊诡的现象:政府越保护中医,民众(尤其是知识阶层)对它的信心反而越低。 知识的黑箱与门槛 不透明的核心机制 西医的"开源"特征首先体现在其底层逻辑的可验证性。一个药物从分子结构、作用靶点、代谢途径到临床疗效,每一步都要发表论文、接受全球同行评审。任何人都可以按照论文中的方法重复实验,验证结果。这就像开源软件的源代码——完全公开,接受任何人的检验和改进。 反观中医,核心理论建立在阴阳五行、

By 王圆圆
隐形的路

隐形的路

亚当和夏娃真的有可能不吃那个禁果吗? 这个争论了几千年的问题,也许本身就问错了方向。真正的问题不是"能不能不吃",而是"为什么我们要假装他们能不吃"。 一个注定失败的考验 让我们诚实地看待伊甸园的设置: 一对还不具备"分辨善恶知识"的存在,被要求判断"违背命令是恶的"。这就像要求一个尚不懂对错的孩子为道德过失承担完全责任。 一棵"悦人眼目"、"能使人有智慧"的树,被种在园子中央。一个会提出质疑的声音,被允许进入。一道禁令,本身就是最好的指路牌。 如果上帝是全知的,那么在创造他们、种下那棵树、允许蛇进入的那一刻,祂就完全知道结果。这很难不让人觉得,整个设置从一开始就不是为了让他们"通过",而是为了让他们"经历"

By 王圆圆