Phorge 现代化改造实战(六):替换实时通知服务,为什么 HTTP 501 反而表示正常
内容提要
本文介绍用Go语言模块gorge-notification替换Phorge的Node.js通知服务Aphlict。该服务通过admin端口接收PHP消息,经WebSocket推送至浏览器,涉及长连接、内存状态和集群防环。文章强调兼容旧协议细节,如501健康状态、错误Content-Type等,并指出服务端全绿不等于链路可用,需真实浏览器验证。
延伸解读
501 状态码的兼容陷阱
Aphlict 的 client 端口对普通 HTTP 请求返回 501,Phorge 的 testClient() 正是依赖这个状态码判断服务健康。若为了统一而将根路径改为 200,Phorge 会误以为服务异常。这提醒我们,兼容旧协议时,某些看似不合理的状态码或响应格式,恰恰是旧客户端依赖的契约,不能随意“优化”。
服务端全绿不等于链路可用
admin 端口探活、集群面板和容器健康检查都正常,不代表浏览器能收到通知。例如 client 地址配置为容器内网名时,服务端一切正常,但浏览器无法解析。这类错误不会出现在服务端日志中,因为连接根本未到达。因此,验证实时通知链路必须从真实浏览器走通 WebSocket,而不能仅依赖服务端指标。
协议兼容中的静默失败风险
Phorge 发送 JSON 消息时未设置正确的 Content-Type,导致 Echo 按表单解析,可能将消息字段揉碎,但服务端仍返回 200 和合法 fingerprint,PHP 认为投递成功,实际浏览器收不到内容。这种静默失败比直接报错更危险,测试中需使用真实 Content-Type 和特殊字符(如 %)来暴露此类问题。
进程拆分与安全边界
gorge-notification 独立成进程,是因为它拥有长连接、内存状态和不同的安全边界(admin 端口无鉴权)。但 admin 和 client 端口必须同进程,因为它们共享 hub 状态。同时,client 端口默认绑定回环地址,避免无鉴权端口暴露;若需公网访问,应通过 TLS 反向代理,并注意 WebSocket 升级和 Cookie 传递。
Q&A
Phorge 中 Aphlict 的作用是什么?
Aphlict 是 Phorge 的实时通知传递层,它接收 PHP 生成的消息,通过 WebSocket 推送给浏览器,但本身不保存业务数据,也不理解消息的业务含义。
为什么 HTTP 501 反而表示 gorge-notification 的 client 端口正常?
因为 Phorge 的 testClient() 会向 client 端口发送普通 HTTP 请求,期望收到 501 状态码(表示需要 WebSocket 升级),如果收到 200 反而会认为服务异常。
gorge-notification 为什么不能与 gorge-render 合并为一个进程?
因为 notification 是有状态的长连接服务,需要保存连接和订阅状态,且两个端口都不能挂鉴权,与 gorge-render 的安全边界不同,合并会导致安全边界难以描述。
为什么 admin 端口不能暴露到公网?
因为为了兼容 Aphlict,admin 端口没有鉴权,暴露出去会允许任何人代替 Phorge 推送任意通知,存在安全风险。
Phorge 发送通知时为什么 Content-Type 是表单,但内容却是 JSON?
因为 Phorge 的 HTTPSFuture 将 JSON 作为裸 body 发送,但没有显式设置 JSON Content-Type,curl 默认将其标为 application/x-www-form-urlencoded。
gorge-notification 如何处理集群中的消息防环?
每个节点启动时生成一个 16 字符的 fingerprint,消息经过节点时将自己的 fingerprint 加入 touched 列表,节点通过检查 touched 避免重复处理。
为什么服务端全绿但用户收不到通知?
因为服务端健康检查、集群面板和容器健康都看不到浏览器是否能解析 client 地址,如果 client 地址配置错误(如使用内网服务名),浏览器无法连接,但服务端日志不会显示错误。
gorge-notification 的 admin 端口成功响应为什么不能使用统一的 {data,error} 信封?
因为 Phorge 的 PHP 代码会直接读取类似 'clients.active' 这样的点号键,如果套用统一信封或改成嵌套结构,解析不会报错但面板计数会变成空白或 0。