前端必须用原生eventsource订阅mercure主题,因其自动重连、解析text/event-stream且唯一兼容;websocket和fetch/axios不适用,前者协议不匹配,后者为短连接无法维持推送流。

前端用 EventSource 订阅 Mercure 主题,别碰 WebSocket 或 fetch
FrankenPHP 的实时推送走的是 Mercure 协议,浏览器端唯一合规、稳定、零依赖的方式就是原生 EventSource。WebSocket 是另一套协议,FrankenPHP 内置的 Mercure Hub 不处理 WebSocket 连接;fetch 或 axios 是短连接,收不到服务端持续推送的 text/event-stream 流——它们根本不会维持连接,更不会自动重连或解析 event 字段。
常见错误现象:
- 用
fetch('/mercure?topic=...')后页面卡住或立刻结束,控制台没报错但收不到任何消息 - 自己封装 WebSocket 客户端,连上后收不到推送,或断连后无法恢复
- 订阅 URL 里手动加
Authorization: Bearer xxx头——浏览器会静默忽略,且EventSource构造函数根本不支持传 headers 参数
正确做法只有一条:用标准 EventSource 实例,让浏览器自动带 cookie 或从 URL 解析 token(取决于后端认证方式):
const es = new EventSource('/.well-known/mercure?topic=https://example.com/notifications');
es.onmessage = (e) => console.log('收到:', e.data);
es.onerror = (err) => console.error('连接异常:', err);
订阅 URL 必须和发布主题字节级一致
Mercure 的 topic 匹配是严格字符串比对,协议、域名、路径、尾部斜杠、编码都必须完全一样。哪怕只差一个 /,前端就收不到推送。
常见错误现象:
- PHP 后端调用
mercure_publish('https://example.com/notifications/', $data),前端却订阅https://example.com/notifications—— 没有消息 - 开发时用
http://localhost,但 Caddyfile 里没加insecure,导致连接被拒绝或返回 401 - 主题含中文或特殊字符(如空格),前端 URL 里没
encodeURIComponent(),后端却用了,造成不匹配
建议统一做法:
前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。
- 前后端共用常量定义 topic,例如
TOPIC_NOTIFICATIONS = 'https://example.com/notifications' - 前端拼接 URL 时显式编码:
new EventSource(`/.well-known/mercure?topic=${encodeURIComponent(TOPIC_NOTIFICATIONS)}`) - 开发环境 Caddyfile 的
mercure块必须包含insecure指令,否则 HTTP 订阅会被拒绝
认证必须走 cookie,不是 Authorization 头
浏览器原生 EventSource 无法发送自定义请求头,所以 Authorization: Bearer xxx 在这里无效。FrankenPHP Mercure Hub 支持从名为 mercureAuthorization 的 cookie 中读取 JWT,这是官方推荐且唯一可行的浏览器端认证方式。
关键点:
- PHP 登录成功后,用
setcookie('mercureAuthorization', $jwt, ['secure' => true, 'samesite' => 'Lax', 'httponly' => false])设置 cookie(注意httponly必须为false,否则 JS 无法触发 EventSource 自动携带) - JWT payload 必须含
{"mercure": {"subscribe": ["https://example.com/notifications"]}},密钥必须和 Caddyfile 中subscriber_jwt完全一致(包括换行、空格) - 前端创建
EventSource时**不要传第二个参数**,也不要手动设 header,让浏览器自动带上 cookie 即可 - Caddyfile 中
mercure块要配置allowed_origins和publish_origins,否则即使 cookie 正确也会被拒绝
断连重试要加退避,否则触发 Caddy 限速
EventSource 默认断连后立即重试,高频重连会快速触达 Caddy 的默认限速(10 次/秒),导致后续连接被拒绝或延迟升高。
解决方法不是关掉重连,而是前端加简单退避逻辑:
let es;
let retryDelay = 1000;
<p>function connect() {
es = new EventSource('/.well-known/mercure?topic=...');
es.onopen = () => { retryDelay = 1000; };
es.onerror = () => {
if (es.readyState === EventSource.CLOSED) {
setTimeout(connect, retryDelay);
retryDelay = Math.min(retryDelay * 2, 30000); // 最大 30 秒
}
};
}
connect();
</p>
这个逻辑能避免雪崩式重连,也符合 Mercure 协议的容错设计。真正容易被忽略的是:很多人以为“连不上是后端问题”,其实只是前端在疯狂撞墙,而 Caddy 已经悄悄把请求挡在外面了。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










