You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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端调整:
    $update = new Update(
        $topic,
        json_encode(['content' => $message]),
        ['type' => 'test-message'] // 自定义事件类型
    );
    
    React端调整:
    eventSource.addEventListener('test-message', (event: any) => {
        console.log('收到消息', JSON.parse(event.data));
    });
    

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 12:19:30