Mercure Hub已接收订阅请求与发布请求,但React订阅端无法接收消息
Mercure Hub已接收订阅请求与发布请求,但React订阅端无法接收消息
嗨,我仔细看了你的问题,咱们一步步来梳理排查哈~ 你已经确认Mercure Hub能正常收到订阅请求和Symfony的发布请求,但React端就是收不到消息,这确实有点挠头,我帮你把现有配置理清楚,再给你列几个重点排查方向:
先复盘你的现有配置(方便对照排查)
Symfony 发布端代码
$update = new Update( $topic, $message ); $hub->publish($update); return $this->json([ 'message' => 'ok', 'mercure' => [ 'topic' => $topic, 'message' => $message ] ]);
React 订阅端代码
const initMercure = async () => { if (!email) { console.error('Cannot Connect', {email}); return; } if (source) { source.close(); } // Getting token const response = await fetch(routeAPI+'/api/test/mercure/generate_token/test'); const jsonResponse = await response.json(); const jwtSubscriber = jsonResponse.token; const mercureUrl = new URL('https://mercure.unifyo.fr/.well-known/mercure?topic=test'); mercureUrl.searchParams.append('authorization', jwtSubscriber); // working, if removed, it will provok a 403 error. // Initi event source const eventSource = new EventSource(mercureUrl.toString(), { // withCredentials: false, // headers: { // not working // 'Authorization': `Bearer ${jwtSubscriber}`, // } }); eventSource.onmessage = (event: any) => { console.log('DATA', event); }; eventSource.onopen = (conn: any) => { console.log('Connexion', conn); } eventSource.onerror = (err: any) => { console.log('Source on error', {err}); eventSource.close(); } setSource(eventSource); } // to publish const publish = async () => { const response = await fetch(`https://api-preprod.unifyo.fr/api/test/mercure/publish/test/jolieMessage`); const text = await response.text() console.log('Publish : ', text); }
Mercure Docker Compose 配置
version: '3.8' services: mercure: image: dunglas/mercure restart: unless-stopped environment: - SERVER_NAME=:80 - MERCURE_UI=1 - MERCURE_UI_URL=https://mercure.unifyo.fr/.well-known/mercure/ui/ - MERCURE_PUBLISHER_JWT_KEY=${MERCURE_PUBLISHER_JWT_KEY} - MERCURE_SUBSCRIBER_JWT_KEY=${MERCURE_SUBSCRIBER_JWT_KEY} - MERCURE_CORS_ALLOWED_ORIGINS=${MERCURE_CORS_ALLOWED_ORIGINS} - MERCURE_ALLOW_ANONYMOUS=1 - MERCURE_DEBUG=1 - MERCURE_EXTRA_DIRECTIVES=trusted_proxies * - MERCURE_EXTRA_DIRECTIVES=jwt.header_authorization - MERCURE_EXTRA_DIRECTIVES=core_origins *
已确认的Mercure日志
订阅请求日志:
{"level":"info","ts":1752242006.4271634,"logger":"http.handlers.mercure","msg":"New subscriber","subscriber":{"id":"urn:uuid:1e19a81f-7a19-4801-b175-21fbf8d9996f","last_event_id":"","remote_addr":"172.20.0.2:44494","topic_selectors":["Test"],"topics":["Test"]}}发布请求日志(状态码200):
{"remote_port":"44508","client_ip":"172.20.0.2","proto":"HTTP/1.1", "method":"POST","host":"mercure.unifyo.fr","uri":"/.well-known/mercure","headers":{"Accept":["*/*"],"X-Forwarded-Server":["9406cad6519b"],"X-Real-Ip":["172.18.0.1"], "User-Agent":["Symfony HttpClient (Curl)"],"Accept-Encoding":["gzip"], "Authorization":["REDACTED"],"X-Forwarded-For":["172.18.0.1"],"X-Forwarded-Port":["443"], "Content-Length":["28"],"Content-Type":["application/x-www-form-urlencoded"], "X-Forwarded-Host":["mercure.unifyo.fr"],"X-Forwarded-Proto": ["https"]}},"bytes_read":28,"user_id":"","duration":0.000926288,"size":45,"status":200,"resp_headers":{"X-Content-Type-Options":["nosniff"],"X-Xss-Protection":["1; mode=block"],"Content-Security-Policy":["default-src 'self'; frame-ancestors 'self'; object-src 'none'"]}}
重点排查这几个核心问题:
1. 主题名称的大小写!(最容易踩的坑)
你看日志里的订阅主题是["Test"](首字母大写),但React代码里写的是topic=test(全小写),Symfony发布的主题是$topic变量。Mercure的主题是严格大小写敏感的,哪怕差一个字母都收不到消息!你赶紧确认下Symfony里的$topic值是test还是Test,把三者统一成完全一致的格式。
2. 订阅JWT的权限是否匹配
你生成的订阅JWT,要确保它的mercure claim里包含了正确的主题(同样要注意大小写)。比如JWT的payload应该类似:
{ "mercure": { "subscribe": ["test"] // 或者"Test",和实际订阅/发布的主题严格一致 } }
你可以用本地的JWT解码工具(别用在线的,安全第一)把拿到的jwtSubscriber解码,确认订阅权限的主题和实际用的完全匹配。
3. 用Mercure官方UI做测试
你已经开启了MERCURE_UI=1,直接访问https://mercure.unifyo.fr/.well-known/mercure/ui/,用这个UI订阅对应主题,然后调用Symfony的发布接口发消息:
- 如果UI能收到消息:说明问题大概率在React代码里
- 如果UI也收不到:说明问题在Mercure Hub或者Symfony发布端
4. 检查Mercure的CORS配置
你的MERCURE_CORS_ALLOWED_ORIGINS用的是环境变量,要确认这个变量的值包含了React应用所在的域名(比如如果React在api-preprod.unifyo.fr,就把这个域名加进去)。虽然现在能发起订阅请求,但CORS配置不全可能导致消息推送失败。
5. React EventSource的小细节调整
- 确保
EventSource实例没有被意外覆盖:在onopen里打印下eventSource的状态,确认连接是正常的,setSource(eventSource)有没有正确更新状态。 - 优化
onerror日志,别着急关闭连接:eventSource.onerror = (err: any) => { console.log('Source on error', {err, readyState: err.target.readyState}); // readyState 0=关闭,1=打开,2=正在连接 if (err.target.readyState === EventSource.CLOSED) { console.log('连接已关闭,可尝试自动重连'); } // 先别急着close,看看是不是临时网络波动 // eventSource.close(); } - 试试监听自定义事件类型:在Symfony发布时指定
type字段,React用addEventListener监听,避免默认message事件的冲突:
Symfony端调整:
React端调整:$update = new Update( $topic, json_encode(['content' => $message]), ['type' => 'test-message'] // 自定义事件类型 );eventSource.addEventListener('test-message', (event: any) => { console.log('收到消息', JSON.parse(event.data)); });
内容来源于stack exchange
相关产品推荐
相关产品推荐

