eventsource连不上主因是php未生成合法subscriber_jwt:密钥须与caddyfile中subscriber_jwt字节级一致,前端请求必须带authorization: bearer ,主题url需绝对匹配。

FrankenPHP里EventSource连不上,基本是JWT没签对
FrankenPHP内置Mercure Hub,走的是标准SSE(EventSource),不是自己搭WebSocket也不是轮询。但很多人配完Caddyfile就以为通了,结果前端new EventSource()直接报401或连接挂起——根本原因不是Caddy没启Mercure,而是PHP后端压根没生成合法的subscriber_jwt。
必须在PHP代码里用openssl_sign()或firebase/php-jwt签发token,密钥要和Caddyfile中subscriber_jwt配置的完全一致(字节级相同,包括换行、空格)。主题URL也要完全一致,比如https://example.com/.well-known/mercure和https://example.com/.well-known/mercure/就是两个不同主题。
- Caddyfile里只写
subscriber_jwt,不等于自动帮你签; - 前端请求头必须带
Authorization: Bearer <token></token>,不能放query参数里; - 测试时用
curl -H "Authorization: Bearer $(php gen_token.php)" https://localhost/.well-known/mercure先验证token是否有效。
短轮询适合低频通知,但别用setInterval硬刷
如果只是每分钟查一次未读数,或者用户离线时不强求实时,短轮询反而更稳。FrankenPHP跑轮询接口毫无压力,但要注意别让setInterval(() => fetch('/api/unread'), 1000)这种写法进生产——每秒一次HTTP请求,100个用户就是100 QPS白耗资源。
- 把轮询间隔拉到5–30秒,视业务容忍延迟而定;
- 加个简单防抖:上一次请求未返回前,跳过下一轮;
- 服务端接口务必加
Cache-Control: no-cache, must-revalidate,避免CDN或浏览器缓存旧数据; - 用
fetch()时显式设cache: 'no-store',别依赖默认行为。
长轮询在FrankenPHP里容易卡死,慎用
FrankenPHP的worker模式本意是常驻执行,但长轮询依赖“挂起响应等事件”,这和它的设计有冲突。一旦PHP脚本用sleep()或stream_select()阻塞太久,Caddy层的timeout或health_timeout可能先触发,直接杀掉worker进程,导致连接中断或502错误。
- FrankenPHP 1.4默认
health_timeout是5秒,长轮询超时设成30秒必须同步调大这个值; - PHP里不能用
set_time_limit(0),Caddy仍会按timeout强制断连; - 比起自己实现长轮询,不如直接切Mercure——它底层就是优化过的长连接+事件广播,更省心。
选SSE还是轮询,关键看三点
不用纠结“哪个技术更先进”,只看实际约束:
- 有没有现成的JWT签发逻辑?没有就先别碰SSE,轮询能跑通再说;
- 通知频率是否大于1次/分钟?高于这个阈值,轮询的带宽和服务器开销就开始明显;
- 客户端是否稳定在线?移动端网络频繁切换时,SSE连接易断且重连逻辑比轮询复杂,这时候轮询反而更鲁棒。
真正容易被忽略的是:Mercure的publisher_jwt和subscriber_jwt必须用同一套密钥体系,且PHP生成token时要用HS256算法——Caddy Mercure只认这个,换成RS256会静默失败,连错误日志都不打。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











