当源端为 PouchDB、目标端为 CouchDB 时,如何处理 CORS 预检导致的长轮询失败?
解读
在国内实际项目中,PouchDB 作为浏览器端数据库常与 CouchDB 做双向同步,但浏览器在发起 长轮询 _changes?feed=longpoll 时会先触发 CORS 预检 OPTIONS 请求。若 CouchDB 的 cors.headers 配置未放行必需字段(如 authorization、content-type、x-requested-with),或 cors.max_age 为 0 导致浏览器频繁预检,就会出现 HTTP 403/401 或 net::ERR_FAILED,表现为同步中断、心跳包无法建立,最终 长轮询失败。面试官通过该题考察候选人对 CORS 协议细节、CouchDB 配置体系、浏览器安全策略 的综合定位与解决能力。
知识点
- CORS 预检机制:非简单请求会先发 OPTIONS,需返回
Access-Control-Allow-Origin、Access-Control-Allow-Headers、Access-Control-Allow-Methods。 - CouchDB 本地.ini 配置段:
[httpd] enable_cors = true、[cors] origins = * | 白名单、credentials = true、headers = accept,authorization,content-type,origin,x-requested-with、methods = GET,PUT,POST,HEAD,DELETE,OPTIONS。 - 长轮询特征:
_changes?feed=longpoll&timeout=25000&heartbeat=10000属于 带凭据的请求,必须满足Access-Control-Allow-Credentials: true且 **origins 不能为通配符 ***。 - 国内云托管限制:部分国产 CouchDB 镜像(如某些国产 Kubernetes 发行版)默认关闭 OPTIONS 响应,需在 ** ingress 或 API 网关层** 额外放行。
- PouchDB 侧降级策略:当检测到
xhr.status === 0且xhr.readyState === 4时,可 回退到 websockets 或轮询模式,但 CouchDB 原生仅支持 HTTP,因此 根本解决仍需服务端配置。
答案
-
修改 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 -k或systemctl reload couchdb,确保 无中断重载。 -
验证 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: true与Access-Control-Allow-Headers: authorization,否则继续排查 反向代理(Nginx、Ingress)是否 吞掉 OPTIONS。 -
反向代理层补充
若前端通过 国内云厂商 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,避免长轮询被缓冲。
-
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% 以上。
拓展思考
- 多节点集群场景:国内金融客户常部署 三中心五节点,若仅配置单节点 cors,跨节点重定向 会导致新 TCP 连接再次预检。解决方法是 把 cors 配置写入全局的 default.ini 并同步到所有节点,或使用 sticky session 保证长轮询不跳转。
- mTLS + CORS 混合场景:在 国密双证书 环境下,浏览器会先完成 TLS 握手再发 OPTIONS,若 证书链缺失 SM2 根证书,表现为 ERR_CERT_INVALID 掩盖了 CORS 失败。需用 国密浏览器(如 360 企业版) 测试,避免误判。
- 性能权衡:
max_age设置过大时,运维改配置后客户端 24h 内不重新预检,不利于热更新;国内互联网节奏建议 max_age=3600 并配合 版本化 origin(如https://v202507.pwa.cn)实现灰度。 - 未来替代方案:考虑 PouchDB 代理插件 pouchdb-adapter-http-alt,通过 Service Worker 拦截 将长轮询转为 WebTransport,可 绕过 CORS,但需 国内 CDN 支持 WebTransport,目前仅阿里云内测,落地前需评估 合规与备案 要求。