news 2026/10/13 3:05:07

在 GitHub Actions 中运行 Mercure Hub 服务容器:集成测试的完整实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在 GitHub Actions 中运行 Mercure Hub 服务容器:集成测试的完整实战指南
  • 后端
  • 即时通讯

【免费下载链接】mercure

🪽 An open, easy, fast, reliable and battery-efficient solution for real-time communications

项目地址:https://gitcode.com/gh_mirrors/me/mercure
点击查看免费下载

本文面向需要在 CI 流水线中验证 Mercure 实时通信逻辑的开发者,讲解如何把 Mercure Hub 以 GitHub Actions 的service container(服务容器)形式拉起,配合内置健康检查完成集成测试,并演示如何在 workflow 内部直接向 hub 发布更新、通知依赖 Mercure 驱动的状态页面。读完本文,你将掌握一套开箱即用、可复制的 GitHub Actions 集成测试配置,并能理解其背后健康检查端点与 JWT 鉴权的实现原理。

场景:为什么用 service container 跑 Mercure Hub

Mercure 是一个基于 Server-Sent Events(SSE)的实时通信协议,Hub 负责接收发布请求并把更新推送给订阅者。在持续集成中,如果被测代码依赖真实的 hub(例如测试"发布→订阅→收到推送"的完整链路),最干净的做法不是 mock 协议,而是在同一个 job 里拉起一个真实的 hub 进程。

GitHub Actions 的 service container 机制正好满足这个需求:它允许你在services段声明一个容器,job 的所有 steps 都能通过localhost访问它,容器由 GitHub 托管运行器负责生命周期管理。Mercure Hub 官方镜像就是一个现成的服务容器,无需额外配置即可在测试前启动、在 job 结束时销毁。

完整工作流示例

将下面这份 YAML 保存为.github/workflows/ci.yml(或你自己的 workflow 文件),即可获得一个带 Mercure Hub 服务容器的集成测试 job:

name: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest permissions: contents: read services: mercure: image: ghcr.io/dunglas/mercure env: SERVER_NAME: ":1337" MERCURE_PUBLISHER_JWT_KEY: "ci-only-publisher-key-never-reuse-it" MERCURE_SUBSCRIBER_JWT_KEY: "ci-only-subscriber-key-never-reuse-it" MERCURE_EXTRA_DIRECTIVES: | anonymous cors_origins * options: >- --health-cmd "wget -q -O /dev/null http://localhost:2019/mercure/health/ready" --health-interval 5s --health-timeout 3s --health-retries 10 ports: - 1337:1337 steps: - uses: actions/checkout@v7 - name: Run tests env: MERCURE_URL: http://localhost:1337/.well-known/mercure run: ./run-tests.sh

工作流触发后,job 的 steps 就能通过http://localhost:1337/.well-known/mercure访问到 hub。这套纯 HTTP 的配置只适用于隔离的 CI 环境(无需 TLS、无需真实域名),这正是它简洁可靠的原因。

逐项拆解服务容器配置

镜像选择:优先 GHCR,而非 Docker Hub

示例使用ghcr.io/dunglas/mercure而非 Docker Hub 上的dunglas/mercure。原因是:GitHub 托管的 runner 拉取 GHCR 镜像无需认证,从而规避 Docker Hub 匿名拉取的速率限制(rate limit)和偶发的认证超时问题。两个仓库里的镜像及 tag 完全相同,如果你偏好 Docker Hub,把image换成dunglas/mercure同样可用——在 Docker 部署文档中可以看到镜像变体的完整说明,其中明确列出ghcr.io/dunglas/mercure与dunglas/mercure是同一镜像同一 tag。

环境变量:把 Caddyfile 的默认值搬进 CI

服务容器内运行的 hub 读取一组环境变量,这些变量在仓库根目录的 Caddyfile 中被声明为 Caddy 占位符:

变量本示例取值作用
SERVER_NAME:1337Caddy 站点地址。:1337表示不绑定主机名、监听 1337 端口(默认是localhost,即 443/80 端口)
MERCURE_PUBLISHER_JWT_KEY任意一次性密钥发布者 token 的签名验证密钥,对应 Caddyfile 中publisher { jwt ... }
MERCURE_SUBSCRIBER_JWT_KEY任意一次性密钥订阅者 token 的签名验证密钥,对应subscriber { jwt ... }
MERCURE_EXTRA_DIRECTIVESanonymous+cors_origins *追加到mercure块内的额外指令,一行一条

anonymous指令允许不带 token的订阅者接收公开更新,这让测试可以省去先签发订阅者 token 的步骤;cors_origins *允许任意跨域来源(不带 cookie 的跨域请求),保证测试页与 hub 不同源时也能建立 SSE 连接。两者的完整语义可参见 Mercure 配置文档 中的指令表与 CORS 一节。

注意:CI 里的 JWT 密钥是"一次性"的——它只存在于这个 job 的生命周期内,绝不与生产密钥混用。示例中的密钥值ci-only-...-never-reuse-it只是占位,你可以直接用openssl rand -base64 32生成真正的随机密钥。

healthcheck:GitHub 等待服务就绪

options段向容器传递 Docker 的 healthcheck 参数:

options: >- --health-cmd "wget -q -O /dev/null http://localhost:2019/mercure/health/ready" --health-interval 5s --health-timeout 3s --health-retries 10

健康检查命令向transport 就绪端点localhost:2019/mercure/health/ready发送 GET 请求。这个端口是 Caddy 的 admin API 默认端口,它绑定在容器回环地址上,因此健康检查在容器内部执行,并不会把 admin API 暴露给 runner。GitHub Actions 会等待服务容器进入 healthy 状态后才开始执行 job steps,从而保证测试开始时 hub 一定可以对外服务。

这条健康检查命令与仓库 Dockerfile 中镜像自带HEALTHCHECK的探测目标完全一致(同样使用wget访问http://127.0.0.1:2019/mercure/health/ready),只是 CI 里把间隔缩短为 5 秒、把重试次数提高到 10,以便在 runner 环境中更快就绪。

不要把订阅端点(/.well-known/mercure的 SSE 订阅)当作就绪探针:订阅请求是长连接,用一个错误响应去判断"未就绪"既不准确,还会污染测试环境。

端口映射

ports: - 1337:1337把容器内 Caddy 监听的 1337 端口映射到 runner 的 localhost 上。hub 的公开 URL 由SERVER_NAME与站点路径共同决定,即http://localhost:1337/.well-known/mercure。

连接细节:issuer 与 audience 的坑

在 CI 中连接 hub 时有一个关键点容易踩坑:token 的签发与校验参数。

由于SERVER_NAME被设为:1337(且未设置MERCURE_TRUSTED_ISSUERS),hub 的默认可信 issuer 是https://localhost(见 Caddyfile 中{$MERCURE_TRUSTED_ISSUERS:https://localhost}的默认值)。同时,hub 会从每个请求推导自己的资源标识符(即 OAuth 2.0 的 audience):客户端访问的是http://localhost:1337/.well-known/mercure,那么测试 token 的aud声明就必须携带http://localhost:1337/.well-known/mercure,而不是 HTTPS 形式,也不是别的端口。

这意味着如果你在测试中签发 token(而不是依赖anonymous指令跳过订阅鉴权),必须:

caddy mercure-token \ --iss https://localhost \ --aud http://localhost:1337/.well-known/mercure \ --key "$MERCURE_PUBLISHER_JWT_KEY" \ --publish 'https://example.com/books/1'

caddy mercure-token是 hub 内置的令牌铸造命令,定义在 caddy/cmdtoken.go,它会生成符合 RFC 9068 的at+jwt访问令牌,自动填充sub、client_id、iat、jti等声明。由于发布与订阅各有一把独立密钥,签发发布者 token 用MERCURE_PUBLISHER_JWT_KEY,订阅者 token 用MERCURE_SUBSCRIBER_JWT_KEY。

hub 校验 token 时会强制检查iss必须匹配可信 issuer、aud必须包含资源标识符、必须有exp,并以配置的密钥验签(算法来自配置而非 token 本身)。完整校验规则见 Mercure 授权文档。

健康检查背后的实现:transport 感知的就绪/存活端点

CI 健康检查使用的端点并非简单的 HTTP 探活,而是transport 感知的:GET /mercure/health/ready在所有 transport 都能提供流量时返回200,否则返回503。它由 Caddy admin API 上的一个模块实现,源码位于 caddy/health.go:

  • 模块注册为admin.api.mercure_health,路由模式为/mercure/health/(第 41-48 行);
  • handleHealth只接受GET(其余方法返回 405),并对每个注册的 hub 遍历其 transport,调用mercure.TransportHealthChecker接口的Ready()/Live()方法(第 130-140 行);
  • transport 不健康时返回503,且响应体只含通用的"status":"error",不会泄露内部连接细节——详细错误只记录在服务端日志(第 82-95 行)。

同族端点还有:

端点语义
GET /mercure/health/ready所有 transport 可服务则200,否则503。用作就绪判断
GET /mercure/health/live所有 transport 基本可用则200。用作存活判断
GET /mercure/health/{name}/ready运行多个 hub 时按名称定向就绪检查
GET /mercure/health/{name}/live按名称定向存活检查

仓库中的 caddy/health_test.go 用真实 Caddy 测试实例验证了这些行为:就绪/存活端点返回"status":"ok"、未知路径返回 404、非 GET 方法返回 405、transport 故障时返回 503 且响应体不含内部错误字符串。

之所以区分 ready 和 live,是因为就绪可能因 transport 临时故障而失败,而存活判断用于区分"可自愈的临时故障"与"需要重启的硬故障"。对于 CI 场景,用ready端点即可:它保证测试开始时 hub 已能接收流量。值得注意的是,捆绑的 BoltDB 与 local transport 没有外部依赖检查,探针恒返回 200(这不验证磁盘容量或端到端投递),但对于本地集成测试来说,能确认 Caddy 与 hub 模块完成加载并正常运行,已经足够。更深入的探针讨论见 健康监控文档。

另外,如果你的测试 job 需要在容器启动后立即发布、而 BoltDB 首次打开需要片刻,官方建议把start_period视为"尚不算不健康"的宽限期(镜像默认 60 秒);本例中通过 10 次重试 × 5 秒间隔兜底。

从 workflow 发布更新:通知与状态同步

除了让测试程序自己调用 hub,你也可以直接在 workflow 里发布更新。典型场景包括:部署完成时通知一个由 Mercure 驱动的状态页面,或者通过自己的 hub 推送一条类 Slack 的通知:

- name: Notify run: | curl --fail-with-body -X POST "$MERCURE_URL" \ -H "Authorization: Bearer $MERCURE_JWT" \ --data-urlencode "topic=https://example.com/deploys/${{ github.repository }}" \ --data-urlencode "data={\"status\":\"deployed\",\"sha\":\"${{ github.sha }}\"}" env: MERCURE_URL: https://hub.example.com/.well-known/mercure MERCURE_JWT: ${{ secrets.MERCURE_PUBLISHER_JWT }}

这个POST请求的协议细节与 发布文档 一致:向/.well-known/mercure发送application/x-www-form-urlencoded表单,字段包括必填的topic和可选的data(hub 把data当作不透明字节,JSON、HTML、纯文本均可),Authorization 头携带Bearertoken。--fail-with-body保证发布失败时 step 会以非零码退出,避免"发布失败但 job 绿了"的假阳性。

这里的两个关键实践:

  1. 发布者 token 必须作用域化:不要用*全量发布权限,而是签发一个只覆盖通知 topic 的 token(例如只允许publish到https://example.com/deploys/*)。授权模型基于authorization_details声明,见 授权文档。
  2. 以 repository secret 存储并定期轮换:把MERCURE_PUBLISHER_JWT存入仓库的 Secrets(${{ secrets.MERCURE_PUBLISHER_JWT }}),到期前轮换。如需自动化轮换,可通过你的授权服务签发短生命周期 token——这正好契合 hub 强制exp声明的设计:短 token 限制了泄露后的影响面。

已有的社区 GitHub Action

社区已有封装好的可复用 Action,把上面的发布调用包装成一个 step:

  • Ilshidur/action-mercure:将向 Mercure hub 发布更新封装为可复用的 Action,适合不想手写curl的场景。

如果你只需要"发布一条更新",用它能让 workflow 更简洁;需要细粒度控制(多 topic、自定义type、private标记等)时,直接使用上面的curl示例更灵活。

实战 Tips 汇总

原文档总结了三条在 GitHub Actions 中使用 Mercure 的实操经验,这里连同补充说明一并列出:

  • 在服务容器内部执行健康检查:示例通过容器的回环地址查询 admin API,既验证了 hub 就绪,又没有把localhost:2019暴露给 runner。注意不要改绑0.0.0.0:2019——那会把整个 admin API(含/stop、/load)暴露到网络。
  • 使用固定端口:1337是约定俗成的选择,请确保它不会与 job 中其他服务容器(如数据库、缓存)的端口冲突。多个服务并存时,给每个服务分配互不重叠的端口并显式映射。
  • 不要在 fork 的 PR 之间共享 JWT:仓库 Secrets 不会暴露给来自 fork 的 PR。如果你的 workflow 会在外部 PR 上执行并向外发布更新,务必把这一点纳入安全设计——要么只在非 fork 分支触发发布步骤,要么使用只读权限的 token。

此外,还可以参考本仓库自身的 .github/workflows/ci.yml:它展示了 GitHub Actions 与 Mercure 结合的另一种形态——在 runner 上直接用go run main.go start --config Caddyfile启动本地 hub,配合playground指令跑 Playwright 一致性测试。如果你在 CI 中需要 hub 的完整开发模式(含调试 UI 与万能 token),可以用MERCURE_EXTRA_DIRECTIVES=playground替代上面的anonymous/cors_origins组合(详见 Docker 部署文档 的开发模式一节;该模式仅限开发环境)。

延伸阅读

  • Mercure 配置文档:全部指令、环境变量与健康检查端点参考
  • Docker 部署文档:镜像变体、Compose 与内置 HEALTHCHECK
  • 健康监控文档:ready/live 端点语义、探针写法与告警建议
  • Mercure 授权文档:token 校验规则与caddy mercure-token用法
  • 发布文档:POST 发布协议与表单字段
  • 后端
  • 即时通讯

【免费下载链接】mercure

🪽 An open, easy, fast, reliable and battery-efficient solution for real-time communications

项目地址:https://gitcode.com/gh_mirrors/me/mercure
点击查看免费下载
上一篇:Zoom 会议实时 AI 集成实战:基于 RTMS 实时媒体流构建转录、情感分析与会议纪要流水线
下一篇:PHP-Parser 入门指南:用 PHP 解析 PHP 代码并构建抽象语法树(AST)

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 3:03:31

MySQL行转列实战:从CASE WHEN到动态SQL的完整攻略

做过报表开发的人几乎都撞上过同一个场景:业务表里明明是一行接一行的明细数据,领导却偏偏要一张横着铺开的宽表,季度在旁边排开、门店在头顶列队、指标一个个填进单元格。我第一次被这个问题卡住的时候,是在一个模拟订单系统里跑…

作者头像 李华
网站建设 2026/10/13 3:03:04

市场观察|信创国产化排队叫号系统采购参考:如何甄别靠谱供应商?

一、验收标准变了政务信创改造推进到现在,排队叫号系统的验收标准已经从“终端能跑”升级为全链路国产化适配。服务器、操作系统、数据库、终端硬件,每一层都要有据可查,合规审计时需提供对应的适配证明。市面上不少产品宣称“支持信创”&…

作者头像 李华
网站建设 2026/10/13 3:02:47

具身智能创新原理(8):赋予具身“原生大脑”深度思考的能力

前沿技术探索:TVA智能体(简称TVA)TVA智能体(亦称“AI智能体视觉”)是依托Transformer架构与“因式智能体”理论构建的新型工业视觉系统,也是当前最具代表性的具身视觉技术之一。它有机融合深度强化学习&…

作者头像 李华
网站建设 2026/10/13 3:02:47

深度学习-极验叠字点选-yolo-模型调优-高阶数学-opencv

一、引言:叠字点选验证码的对抗本质极验叠字点选与传统点选验证码的差异:字形重叠、语义干扰、动态渲染深度学习破解的总体技术路线:目标检测 语义匹配 色彩处理文章目标:聚焦YOLO系列模型在叠字场景下的检测精度提升与数学优化方…

作者头像 李华
网站建设 2026/10/13 3:02:13

短剧硬字幕如何批量 OCR:从字幕区到整剧校对

短剧硬字幕如何批量 OCR:从字幕区到整剧校对 先说结论 先抽取开场、普通对白、运动背景和多人场景,分别标出对白字幕、片名、花字与弹幕区域。固定字幕区可批量识别,移动或重叠文字要分组处理。输出后按集序校对时间轴、人名、断句和重复帧&a…

作者头像 李华