mercure.Publish() 的 topic 参数必须是绝对 URL;需严格匹配前端订阅的完整 URL(含协议、主机、路径、大小写及编码),仅支持 RFC 3986 格式,不接受相对路径或自定义标识符。

mercure.Publish() 的 topic 参数必须是绝对 URL
FrankenPHP 内置 Mercure Hub 要求所有消息主题(topic)必须是符合 RFC 3986 的绝对 URL,不能是相对路径、字符串或自定义标识符。这是协议强制要求,不是 FrankenPHP 的限制——底层 Mercure 规范本身只接受 https://example.com/posts/123 这类格式。
常见错误现象:mercure.Publish() 返回 error: invalid topic 或 HTTP 400 响应体含 "invalid topic";浏览器端 EventSource 连接直接失败且无日志。
- 正确写法:
"https://api.example.com/users/42"、"https://localhost:8000/notifications" - 错误写法:
"users/42"、"notifications"、"urn:topic:alerts"、"topic:news" - 开发阶段可用
http://,但生产环境必须用https://(浏览器 EventSource 会拒绝非安全上下文下的 http 主题)
主题命名要和前端订阅地址严格一致
前端用 new EventSource("https://localhost:8000/.well-known/mercure?topic=https%3A%2F%2Fapi.example.com%2Fposts%2F123") 订阅,后端 mercure.Publish() 就必须传完全相同的 URL 字符串——包括协议、主机、路径、大小写、尾部斜杠(有无都算不同主题)。
容易踩的坑:PHP 中拼接 URL 时漏掉 https:// 前缀、用 $_SERVER['HTTP_HOST'] 拼出的域名没带端口(本地开发时 localhost:8000 和 localhost 不等价)、URL 编码不一致(如空格被转成 + 而非 %20)。
立即学习“PHP免费学习笔记(深入)”;
- 推荐做法:统一用
parse_url()+http_build_query()构造完整订阅 URL,再提取topic=后的原始值作为发布参数 - 调试技巧:在
mercure.Publish()前加var_dump($topic); die;,把输出和浏览器 Network 面板里实际请求的topic查询参数逐字符比对
支持通配符主题但需显式启用
Mercure 允许用 * 和 ** 匹配多级路径,例如 "https://api.example.com/posts/*" 可匹配 /posts/123 和 /posts/456/comments,但该功能默认关闭,必须在 Caddyfile 的 mercure 块中添加 publish_origins 并显式列出允许通配的源。
性能影响:通配符主题会增加 Hub 的路由匹配开销,高并发推送时可能比精确主题慢 10–15%,不建议在每秒数百次推送的场景滥用。
- 启用示例:
mercure { publisher_jwt !ChangeThisMercureHubJWTSecretKey! subscriber_jwt !ChangeThisMercureHubJWTSecretKey! anonymous publish_origins ["https://api.example.com"] } - 发布时仍需传完整 URL:
mercure.Publish("https://api.example.com/posts/123", $payload),通配符只控制谁有权发布,不改变 topic 格式要求
不要在 topic 里塞动态数据以外的内容
topic 应该只承载资源标识,不要混入操作语义或状态标记。比如用 "https://api.example.com/orders/789" 表示订单变更,而不是 "https://api.example.com/orders/789/status_changed" 或 "https://api.example.com/orders/789?event=updated"。
原因:Mercure 的设计哲学是“一个资源一个主题”,事件类型(created/updated/deleted)应通过消息 payload 区分,而非 topic。否则会导致订阅者无法复用连接,也违背 RESTful 资源模型。
- 正确结构:
$payload = json_encode([ 'type' => 'updated', 'data' => ['status' => 'shipped'] ]);
- 错误结构:把
type拼进 topic 字符串,导致主题爆炸式增长,Hub 内存占用不可控



















