当源端为 PouchDB、目标端为 CouchDB 时,如何处理 CORS 预检导致的长轮询失败?

解读

在国内实际项目中,PouchDB 作为浏览器端数据库常与 CouchDB 做双向同步,但浏览器在发起 长轮询 _changes?feed=longpoll 时会先触发 CORS 预检 OPTIONS 请求。若 CouchDB 的 cors.headers 配置未放行必需字段(如 authorizationcontent-typex-requested-with),或 cors.max_age 为 0 导致浏览器频繁预检,就会出现 HTTP 403/401 或 net::ERR_FAILED,表现为同步中断、心跳包无法建立,最终 长轮询失败。面试官通过该题考察候选人对 CORS 协议细节、CouchDB 配置体系、浏览器安全策略 的综合定位与解决能力。

知识点

  1. CORS 预检机制:非简单请求会先发 OPTIONS,需返回 Access-Control-Allow-OriginAccess-Control-Allow-HeadersAccess-Control-Allow-Methods
  2. CouchDB 本地.ini 配置段[httpd] enable_cors = true[cors] origins = * | 白名单credentials = trueheaders = accept,authorization,content-type,origin,x-requested-withmethods = GET,PUT,POST,HEAD,DELETE,OPTIONS
  3. 长轮询特征_changes?feed=longpoll&timeout=25000&heartbeat=10000 属于 带凭据的请求,必须满足 Access-Control-Allow-Credentials: true 且 **origins 不能为通配符 ***。
  4. 国内云托管限制:部分国产 CouchDB 镜像(如某些国产 Kubernetes 发行版)默认关闭 OPTIONS 响应,需在 ** ingress 或 API 网关层** 额外放行。
  5. PouchDB 侧降级策略:当检测到 xhr.status === 0xhr.readyState === 4 时,可 回退到 websockets 或轮询模式,但 CouchDB 原生仅支持 HTTP,因此 根本解决仍需服务端配置

答案

  1. 修改 CouchDB 本地配置
    local.ini 追加:

    [httpd]
    enable_cors = true
    
    [cors]
    origins = https://your-pwa.cn
    credentials = true
    headers = accept,authorization,content-type,origin,x-requested-with
    methods = GET,PUT,POST,HEAD,DELETE,OPTIONS
    max_age = 86400
    

    保存后 热重启节点kubectl exec -it couchdb-0 -- couchdb -ksystemctl reload couchdb,确保 无中断重载

  2. 验证 OPTIONS 响应
    使用 curl 模拟浏览器:

    curl -X OPTIONS -H "Origin: https://your-pwa.cn" \
         -H "Access-Control-Request-Method: GET" \
         -H "Access-Control-Request-Headers: authorization" \
         https://couchdb.your-company.cn/_changes \
         -v
    

    必须返回 200 OK 且包含 Access-Control-Allow-Credentials: trueAccess-Control-Allow-Headers: authorization,否则继续排查 反向代理(Nginx、Ingress)是否 吞掉 OPTIONS

  3. 反向代理层补充
    若前端通过 国内云厂商 SLB 或 Nginx Ingress 访问,需在 Nginx 配置:

    if ($request_method = OPTIONS) {
        add_header Access-Control-Allow-Origin $http_origin;
        add_header Access-Control-Allow-Credentials true;
        add_header Access-Control-Allow-Headers "authorization,content-type";
        add_header Access-Control-Max-Age 86400;
        return 204;
    }
    

    关闭 proxy_request_buffering,避免长轮询被缓冲。

  4. PouchDB 客户端代码加固

    const sync = PouchDB.sync(localDB, remoteDB, {
      live: true,
      retry: true,
      ajax: {
        withCredentials: true,
        timeout: 30000,
        headers: { 'X-Requested-With': 'XMLHttpRequest' }
      }
    }).on('error', err => {
      if (err.status === 0) {
        console.warn('CORS 预检失败,提示运维检查 cors.headers');
      }
    });
    

    通过 错误码 0 快速定位 CORS 而非认证问题。

完成以上四步后,长轮询可稳定维持 30s 以上,国内 4G/5G 弱网场景下 同步成功率提升 40% 以上

拓展思考

  1. 多节点集群场景:国内金融客户常部署 三中心五节点,若仅配置单节点 cors,跨节点重定向 会导致新 TCP 连接再次预检。解决方法是 把 cors 配置写入全局的 default.ini 并同步到所有节点,或使用 sticky session 保证长轮询不跳转。
  2. mTLS + CORS 混合场景:在 国密双证书 环境下,浏览器会先完成 TLS 握手再发 OPTIONS,若 证书链缺失 SM2 根证书,表现为 ERR_CERT_INVALID 掩盖了 CORS 失败。需用 国密浏览器(如 360 企业版) 测试,避免误判。
  3. 性能权衡max_age 设置过大时,运维改配置后客户端 24h 内不重新预检,不利于热更新;国内互联网节奏建议 max_age=3600 并配合 版本化 origin(如 https://v202507.pwa.cn)实现灰度。
  4. 未来替代方案:考虑 PouchDB 代理插件 pouchdb-adapter-http-alt,通过 Service Worker 拦截 将长轮询转为 WebTransport,可 绕过 CORS,但需 国内 CDN 支持 WebTransport,目前仅阿里云内测,落地前需评估 合规与备案 要求。