frankenphp已原生集成mercure hub,但必须在caddyfile中显式启用mercure指令块并正确配置jwt密钥、publish_origins、cors_origins及insecure(开发时),否则前端订阅401、后端发布403频发。

不需要单独部署 Mercure Hub,FrankenPHP 已原生集成,但必须显式启用并正确配置 JWT 权限和 Caddy 路由,否则前端连不上、后端发不出、401/403 频发。
在 Caddyfile 中启用并配置 Mercure
FrankenPHP 的 Mercure 不是插件,而是编译进二进制的 Go Hub,必须在 Caddyfile 里声明 mercure 指令块才能激活。漏掉这一步,整个 Mercure 就不工作。
-
publish_origins必须显式列出允许发布事件的源(如["https://your-domain.com"]),否则 Symfony 的MercureBundle发布时会因 CORS 被拒 -
cors_origins控制前端订阅是否被接受,开发时若用http://localhost,需加insecure指令,否则 EventSource 连接直接 401 - 不要手动设置
jwt_key—— FrankenPHP 默认自动生成密钥;若真要自定义,必须通过FRANKENPHP_MERCURE_JWT_KEY环境变量,且与 PHP 签名时用的密钥字节级一致(含换行、空格) - 避免在
Caddyfile里写全局redir https://{host}{uri} permanent,它会把前端 SSE 订阅请求 301 重定向,导致长连接中断
生成合法的 subscriber JWT 给前端
前端 EventSource 订阅不是裸连,必须带 Authorization: Bearer <token></token>,而这个 token 必须由 PHP 后端签发,payload 中 mercure.subscribe 字段不能为空数组且必须是绝对 URI 数组。
- JWT 算法仅支持
HS256,不能用RS256或ES256 - payload 至少包含:
{"mercure": {"subscribe": ["https://your-domain.com/notifications"]}}—— 注意是字符串数组,不是单个字符串 - 主题 URL 必须是绝对 URI,
*只能出现在路径末尾,例如https://your-domain.com/posts/*✅,https://your-domain.com/*/feed❌ - 密钥必须和 FrankenPHP 内部使用的完全一致:如果 Caddyfile 用环境变量
FRANKENPHP_MERCURE_JWT_KEY=abc123,PHP 里就得用openssl_sign($payload, $key, 'sha256'),且$key是原始字符串,不是 base64 解码后的内容(除非你两边都统一处理)
从 PHP 发布事件到 Mercure Hub
发布不是调用某个函数,而是向 http://localhost/.well-known/mercure 发起带签名的 POST 请求。FrankenPHP 的本地回环路径绕过网络栈,但权限控制极严,错一个字段就 403。
- POST 请求头必须含
Authorization: Bearer <publisher_token></publisher_token>,该 token 的 payload 必须有mercure.publish字段(同样为字符串数组) - 推荐用原生
cURL而非 Guzzle:FrankenPHP worker 对连接复用敏感,Guzzle 可能因复用旧连接导致超时;显式设CURLOPT_TIMEOUT_MS = 200更稳妥 - 不要在控制器里用
exec('curl ...')或file_get_contents()—— 前者开子进程污染 worker,后者默认无超时,可能卡死整个请求生命周期 - Symfony
MercureBundle的HubInterface::publish()在 FrankenPHP 下仍可用,但它底层仍是发 HTTP 请求;确保hub_url: 'http://localhost/.well-known/mercure',且.env中删掉MERCURE_PUBLISH_URL和MERCURE_JWT_KEY
Symfony 配置和常见踩坑点
很多问题表面是 Mercure 不通,实际是 Symfony 层配置与 FrankenPHP 运行模型冲突。最典型的三个动作必须做,缺一不可。
- 删掉
docker-compose.yml里独立的mercureservice —— FrankenPHP 默认占:80/:443,外部 Mercure 通常抢:3000,端口冲突直接导致启动失败 - 注释或删除
.env中的MERCURE_PUBLISH_URL和MERCURE_JWT_KEY—— FrankenPHP 不读这两个变量,用了反而干扰 -
config/packages/mercure.yaml中hub_url必须设为'http://localhost/.well-known/mercure',这是唯一能让 Symfony 组件走本地回环的地址;生产环境不要配成公网域名,否则请求会出容器 - 若用
symfony/webhook-bundle或基于 ReactPHP 的异步组件,务必禁用 —— 它们的事件循环会和 FrankenPHP 的 Go runtime 争抢资源,引发stream_select(): unable to select
真正难的不是写代码,而是让 JWT 密钥、Caddy 配置、Symfony Bundle 设置、前端订阅 URL 四者在字节级上完全对齐;任意一处多一个空格、少一个 https://、错一个斜杠,Mercure 就静默失败。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











