EventSource连不上Mercure,首先确认Caddyfile已启用mercure块并配置非空publisher_jwt和subscriber_jwt;开发用localhost需加insecure指令;curl访问/.well-known/mercure应返回含@id的JSON才算启用成功。

EventSource 连不上 Mercure?先确认 Caddyfile 是否启用 Hub
FrankenPHP 内置的 Mercure Hub 默认是关闭的,EventSource 请求会直接 404 或 500。必须在 Caddyfile 中显式启用 mercure 指令块,并配置密钥:
-
mercure块不能被注释,且需包含publisher_jwt和subscriber_jwt两段(密钥可相同,但不能为空) - 开发时若用
http://localhost,必须加insecure指令,否则 Mercure 拒绝 HTTP 订阅 - 检查是否生效:用
curl -v http://localhost/.well-known/mercure,返回 JSON(含@id,@type)才算启用成功
前端 new EventSource() 报 401?JWT token 没签或不匹配
EventSource 不是裸连,必须带 Authorization: Bearer <token>,而这个 token 必须由 Caddyfile 中的 subscriber_jwt 密钥签名,且 payload 至少含 mercure.subscribe 字段:
- PHP 后端生成 token 时,要用
openssl_sign()或firebase/php-jwt,不能手拼 base64 - 密钥必须字节级一致:Caddyfile 中的密钥若有换行或空格,PHP 端要用
base64_decode()处理后再传入签名函数 - 主题 URL 必须完全一致——
https://example.com/notifications和https://example.com/notifications/是两个 topic - 浏览器控制台看到
Failed to load resource: the server responded with a status of 401,基本就是 token 签发失败或密钥错
收到事件但只一次就断开?重连逻辑没处理或被限速
原生 EventSource 在网络抖动、服务重启后会自动重试,但默认策略太激进,容易触发 Caddy 的速率限制(默认 10 次/秒),导致后续连接被拒绝:
- 不要依赖默认重连:在
onerror回调里加退避逻辑,比如首次 1s 后重试,失败则指数退避到 30s - 避免多个标签页同时重连:每个订阅实例应带唯一 query 参数(如
?uid=123),方便后端调试和限流区分 - Chrome 单域名最多 6 个
EventSource连接,开太多标签页会排队或失败,这不是 Mercure 问题,是浏览器策略 - 若页面长期驻留,建议监听
onopen后发送一次心跳请求(如 GET/api/health),验证通道可用性
PHP 后端发了消息但前端收不到?发布路径、token、topic 全链路要对齐
前端能连上 ≠ 能收到推送。发布端(PHP)和订阅端(EventSource)必须使用同一套主题 URL、同一套 JWT 密钥、同一套网络路径:
立即学习“PHP免费学习笔记(深入)”;
- 发布必须走
POST /.well-known/mercure,不是自定义路由;body 必须是 JSON,含data和topic字段(或topics数组) - 发布的
Authorizationheader 用的是publisher_jwt签的 token,和前端订阅用的subscriber_jwttoken 完全无关 - Docker 环境下 PHP 代码里 curl 的目标地址不能写
localhost,得用http://host.docker.internal:8080(Mac/Windows)或http://172.17.0.1:8080(Linux) - 最简验证法:用
curl -N "http://localhost/.well-known/mercure?topic=https://example.com/test"开一个终端监听,再另起终端用curl -X POST ...发布,看是否立刻打印出 event



















