直接用curl向/.well-known/mercure发POST请求即可发布消息,但必须确保JWT含mercure.publish字段、URL可达、topic字节级匹配;file_get_contents因不支持自定义Authorization头与JSON body组合且无法精确控超时而易失败。

直接用 curl 向 /.well-known/mercure 发 POST 请求就能发布,不需要 SDK、不依赖 Symfony、也不用 FrankenPHP 的 Go API 封装 —— 但必须确保 JWT 正确、URL 可达、主题匹配。
为什么 file_get_contents 容易失败
它默认不支持自定义 Authorization 头 + JSON body 的组合写法,且无法显式控制超时和连接复用。很多 PHP 开发者试了几次 file_get_contents 返回空或 401 就放弃,其实不是协议问题,是调用姿势不对。
- 必须手动构造流上下文(
stream_context_create),否则头字段被忽略 - 响应体为空时不会报错,容易误判为“成功”
- 没有
CURLOPT_TIMEOUT_MS级别的毫秒级超时,长连接卡住会拖慢整个请求生命周期 - FrankenPHP Worker 模式下,
file_get_contents可能复用底层连接,导致 Mercure Hub 认为是同一订阅者反复重连
curl 发布的最小可靠代码
以下代码在 FrankenPHP 容器内可直接运行,已避开常见网络和 JWT 坑点:
$url = 'http://host.docker.internal:8080/.well-known/mercure';
$token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJtZXJjdXJlIjp7InB1Ymxpc2giOlsiaHR0cHM6Ly9leGFtcGxlLmNvbS9ub3RpZmljYXRpb25zIl19fQ.xxxx'; // publisher token
$data = json_encode([
'data' => 'new notification',
'type' => 'notification',
'topic' => 'https://example.com/notifications'
]);
$headers = [
'Authorization: Bearer ' . $token,
'Content-Type: application/json',
'Accept: text/event-stream'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 200); // 关键:防止阻塞
curl_setopt($ch, CURLOPT_FAILONERROR, true);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 201) {
error_log("Mercure publish failed: $httpCode");
}
-
host.docker.internal是 Mac/Windows Docker Desktop 的宿主机别名;Linux 下请换为http://172.17.0.1:8080 -
CURLOPT_TIMEOUT_MS = 200是硬性建议:Mercure 发布本应极快,超时说明配置或网络有问题 - 不要省略
CURLOPT_FAILONERROR,否则 4xx/5xx 仍返回true,掩盖错误 -
topic字段不是必需的,但若加了,必须和前端EventSource订阅的 URL 字节级一致(含协议、端口、尾部斜杠)
JWT 必须含 mercure.publish 字段
FrankenPHP Mercure 不认 publisher 这种旧字段,只解析 payload 里的 mercure.publish 数组。用错字段会稳定返回 403。
立即学习“PHP免费学习笔记(深入)”;
- 正确结构:
{"mercure": {"publish": ["https://example.com/notifications"]}} - 密钥必须和 Caddyfile 中
publisher_jwt配置完全一致(包括换行符、base64 编码方式) - 签名算法只能是
HS256,FrankenPHP 不支持RS256或ES256 - 生成命令示例:
echo '{"mercure":{"publish":["https://example.com/notifications"]}}' | openssl dgst -sha256 -hmac "your-key" -binary | openssl enc -base64
开发环境连不通?先查这三件事
很多“发布没反应”其实是前端根本没连上 Mercure,而不是发布失败。
- 访问
http://localhost/.well-known/mercure,应返回 JSON discovery 文档(含hub,rel字段),不是 404 或 500 - 前端
EventSourceURL 必须带Authorization: Bearer <subscriber_token>,不能只靠 cookie 或 session - Caddyfile 中 mercure 块必须有
insecure(开发用 localhost 时),否则会拒绝 HTTP 订阅
真正难的不是发一次消息,而是让每次发布都落到正确的 topic、被所有匹配的 subscriber 收到 —— 主题 URI 的拼写、JWT 字段语义、容器网络路径,三者只要一个字节不对,就静默失败。



















