ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

FreshRSS WebSub 订阅数据目录全解析:`data/PubSubHubbub/feeds` 目录结构与推送机制

FreshRSS WebSub 订阅数据目录全解析:`data/PubSubHubbub/feeds` 目录结构与推送机制 FreshRSS WebSub 订阅数据目录全解析data/PubSubHubbub/feeds目录结构与推送机制【免费下载链接】FreshRSSA free, self-hostable news aggregator…项目地址: https://gitcode.com/gh_mirrors/fr/FreshRSSFreshRSS 原生支持 WebSub原名 PubSubHubbub下称 PuSH协议让订阅源的新文章能够被 Hub 以「推送」方式即时送达而不是依赖轮询拉取。本文以仓库中 data/PubSubHubbub/feeds/README.md 描述的目录结构为骨架结合 Feed.php、p/api/pshb.php 与 config.default.php 的源码实现深入讲解 FreshRSS 如何以文件系统为「数据库」维护 WebSub 订阅状态包括目录布局、密钥文件、订阅/退订/续租的完整生命周期以及推送回调的安全校验逻辑。读完本文你将能够读懂自己实例中data/PubSubHubbub下的每一个文件并能独立排查 WebSub 失效、租约过期、推送回调失败等常见问题。一、feeds 目录在 WebSub 中的定位data/PubSubHubbub/feeds/是 FreshRSS 用于登记「本实例中所有用户已订阅的 feed 的规范化 URLcanonical URL」的目录。其 README 原文明确了两点设计意图去重List of canonical URLs of the various feeds users have subscribed to——多个用户可能订阅同一个 feed目录结构需要让同一个 feed 只维护一份订阅状态多用户共享Several users can have subscribed to the same feed——目录内以「每个用户一个标记文件」的方式记录有哪些用户共享了这次订阅。它对应源码中的常量PSHB_PATH// constants.php defined(PSHB_PATH) or define(PSHB_PATH, DATA_PATH . /PubSubHubbub);其中DATA_PATH默认是FRESHRSS_PATH . /data可由环境变量DATA_PATH覆盖因此实际路径即data/PubSubHubbub/与 constants.php 中的定义一致。二、feeds 目录的完整结构README 描述的结构如下data/PubSubHubbub/feeds/ └── canonicalUrl 的编码/ # 每个 feed 一个子目录 ├── !hub.json # 该 feed 的 Hub 订阅元数据 ├── user1.txt # 订阅了该 feed 的用户标记 └── user2.txt三个要素逐一拆解2.1 子目录名canonical URL 的哈希README 中写的是base64url(canonicalUrl)但从当前仓库源码看实际目录名使用sha1()十六进制哈希。在 Feed.php 的pubSubHubbubPrepare()中$path PSHB_PATH . /feeds/ . sha1($this-selfUrl); $hubFilename $path . /!hub.json;在 p/api/pshb.php 的推送回调中同样如此$canonicalHash sha1($canonical); $hubFile file_get_contents(feeds/ . $canonicalHash . /!hub.json);也就是说无论 README 早期版本如何描述当前代码以sha1(canonicalUrl)作为子目录名。使用哈希而非直接使用 URL 的原因很直观URL 中可能包含/、?、#等不能直接作为文件路径的字符哈希则恒定为 40 个十六进制字符天然安全且固定长度。selfUrl即 feed 的规范化 URL来自 feed 内容或 HTTP 头中relself的链接。2.2!hub.json单份订阅元数据每个 feed 子目录下的!hub.json是该 feed 的 WebSub 订阅状态机字段由源码写入与读取字段类型含义hubstring该 feed 声明的 Hub 地址来自 feed 中的link relhubkeystring本实例为此次订阅生成的密钥用于回调 URL 鉴权见第三节lease_startint租约lease开始时间戳用于防止过频繁的续租lease_endint租约到期时间戳由 Hub 在订阅确认时返回的hub_lease_seconds决定errorbool是否处于错误状态推送验证成功前为true首次成功推送后置为false创建时机在pubSubHubbubPrepare()首次准备订阅时$key sha1($path . FreshRSS_Context::systemConf()-salt); $hubJson [ hub $this-hubUrl, key $key, ]; file_put_contents($hubFilename, json_encode($hubJson));注意key由sha1(feed目录路径 系统 salt)生成salt来自安装时生成的 config.default.php 中的salt配置项。这使得密钥即使泄露也无法在没有 salt 的情况下反推其他 feed 的密钥。error标志的语义在pubSubHubbubEnabled()与pubSubHubbubError()中有明确实现Feed.phppublic function pubSubHubbubEnabled(): bool { ... if (is_array($hubJson) empty($hubJson[error]) (empty($hubJson[lease_end]) || $hubJson[lease_end] time())) { return true; } ... }即「WebSub 生效」!hub.json存在 error为空 租约未过期三个条件同时满足。任一不满足FreshRSS 就会退回轮询拉取。2.3 用户标记文件user1.txt、user2.txt目录下每个订阅用户对应一个以其用户名为文件名的空文件内容为空仅作标记。它由pubSubHubbubPrepare()创建$currentUser Minz_User::name() ?? ; if (FreshRSS_user_Controller::checkUsername($currentUser) !file_exists($path . / . $currentUser . .txt)) { touch($path . / . $currentUser . .txt); }而在推送回调 p/api/pshb.php 中FreshRSS 通过扫描glob(*.txt)得到全部订阅用户从而回应 README 中「多用户共享同一订阅」的设计$users glob(*.txt, GLOB_NOSORT); if (empty($users)) { // 没有任何用户订阅了清理整个目录并退订 Hub $feed new FreshRSS_Feed($canonical); $feed-pubSubHubbubSubscribe(false); unlink(!hub.json); recursive_unlink(feeds/ . $canonicalHash); }这带来一个重要行为多个用户订阅同一个 feed 时FreshRSS 只向 Hub 订阅一次Hub 推送一次内容后由本实例分发fan-out给所有相关用户而不是每个用户各自订阅有效减少 Hub 与实例间的重复流量。三、配套的 keys 目录订阅凭据管理理解 feeds 目录离不开其兄弟目录 data/PubSubHubbub/keys/README 描述其结构为data/PubSubHubbub/keys/ └── sha1(random salt).txt # 文件名即订阅密钥 └── 内容canonical URL结合源码keys/{key}.txt的内容实际是canonical URL 的纯文本README 中base64url(canonicalUrl)的表述在现版本中对应为直接写明文 URL。写入发生在pubSubHubbubPrepare()mkdir(PSHB_PATH . /keys/, 0770, true); file_put_contents(PSHB_PATH . /keys/ . $key . .txt, $this-selfUrl);它构成了回调验证的第一环Hub 在验证订阅或推送内容时会携带 FreshRSS 在订阅请求中提交的hub.callback即{base_url}/api/pshb.php?k{key}。回调端 p/api/pshb.php 依次执行格式校验k参数必须是十六进制字符且长度不超过 128否则返回422密钥查找读取keys/{key}.txt得到 canonical URL文件不存在返回410 Gone并特别允许unsubscribe模式的宽松处理交叉校验用sha1(canonical)定位feeds/{hash}/!hub.json核对其中key字段与回调携带的key完全一致防止伪造回调不一致返回500。$hubJson json_decode($hubFile, true); if (!is_array($hubJson) || empty($hubJson[key]) || $hubJson[key] ! $key) { header(HTTP/1.1 500 Internal Server Error); die(Invalid key cross-check!); }三层校验环环相扣任何一个环节失败都会拒绝处理且失败路径上会自动清理失效文件如unlink(keys/ . $key . .txt)。四、订阅、续租与退订目录文件的生命周期4.1 订阅Subscribe订阅在 feedController.php 的 feed 添加流程中触发逻辑分两步if ($pubsubhubbubEnabledGeneral $feed-pubSubHubbubPrepare() ! false) { if (!$feed-pubSubHubbubSubscribe(true)) { //Subscribe ... } }pubSubHubbubPrepare()本地落盘准备——创建feeds/{sha1}/!hub.json、keys/{key}.txt、用户标记文件pubSubHubbubSubscribe(true)向 Hub 发送 HTTP 订阅请求参数为Feed.phphub.verifysync hub.modesubscribe hub.topicfeed 的 canonical URL hub.callback{base_url}/api/pshb.php?k{key}回调 URL 由Minz_Request::getBaseUrl() . /api/pshb.php?k . $hubJson[key]拼装因此系统配置base_url必须能被 Hub 公网访问否则订阅无法建立。4.2 租约Lease与续租Hub 收到订阅请求后会回调 FreshRSS 的回调端点进行验证。在 p/api/pshb.php 的hub_modesubscribe分支中if ($leaseSeconds 60) { $hubJson[lease_end] time() $leaseSeconds; } else { unset($hubJson[lease_end]); } $hubJson[lease_start] time(); if (!isset($hubJson[error])) { $hubJson[error] true; //Do not assume that WebSub works until the first successful push }Hub 返回的hub_lease_seconds被记录为lease_end小于等于 60 秒的租约视为无效直接不设到期时间同时error被置为true——在收到第一次成功推送之前FreshRSS 不认为 WebSub 已经生效。续租策略在pubSubHubbubPrepare()中每次拉取/刷新时被调用检查if (!empty($hubJson[lease_end]) $hubJson[lease_end] (time() (3600 * 23))) { $key $hubJson[key]; //To renew our lease } elseif (((!empty($hubJson[error])) || empty($hubJson[lease_end])) (empty($hubJson[lease_start]) || $hubJson[lease_start] time() - (3600 * 23))) { $key $hubJson[key]; //To renew our lease }即租约将在23 小时内到期、或状态异常且距上次尝试超过 23 小时时会复用既有key重新发起订阅以续租。源码中标注了TODO: Make a better policy说明该阈值目前是硬编码策略。4.3 退订Unsubscribe与清理删除 feed 时feedController.php 调用pubSubHubbubSubscribe(false)逻辑上先将lease_end置为过去时间time() - 60阻止后续续租Feed.php再向 Hub 发送hub.modeunsubscribe请求。Hub 侧的退订验证同样会打到回调端点hub_modeunsubscribe分支会核对租约状态p/api/pshb.php若租约已过期则直接应答hub_challenge否则返回422「We did not ask to unsubscribe!」。当最后一个用户取消订阅后glob(*.txt)结果为空回调会自动清理整个feeds/{sha1}/目录与keys/{key}.txt实现无残留的自我回收。五、一次完整的推送分发Fan-out流程当发布者发布新内容Hub 将内容 POST 到 FreshRSS 的回调端点p/api/pshb.php。除去订阅/退订验证分支内容推送的处理链路如下p/api/pshb.php读取并解析负载MAX_PAYLOAD限制为 3,145,728 字节约 3 MB防止超大请求拖垮实例空负载返回422提取 self 链接用FreshRSS_SimplePieCustom解析 XML 中的link relself同时支持 HTTP 头Link: ...; relself两处 URL 不一致时仅记录警告不拒绝见compareUrlIgnoringHttps逐个用户分发遍历glob(*.txt)得到的用户名列表逐个初始化用户上下文并调用[$nbUpdatedFeeds, ] FreshRSS_feed_Controller::actualizeFeedsAndCommit( feed_url: $canonical, simplePiePush: $simplePie, selfUrl: $self);actualizeFeedsAndCommit是 feedController.php 中核心的 feed 实际化方法推送模式下simplePiePush参数传入解析好的内容直接落库而不再发起网络拉取 4.失效用户清理若某用户已不再订阅该 feed返回 0 个更新则删除其标记文件unlink($userFilename)若用户配置不存在或已禁用!enabled同样删除并跳过 5.状态翻转若至少一个用户成功更新且!hub.json中error为true则写回error: false标志着「WebSub 已确认可用」 6.结果响应返回Done: N日志记录WebSub canonical done: nb。值得注意的兜底设计在 feedController.php 中若 feed 处于 WebSub 推送状态却在轮询拉取时发现新文章会触发pubSubHubbubError(true)将error置位——这意味着「推送漏了内容」下次刷新会触发续租重新订阅形成自愈闭环。六、启用前置条件与配置项WebSub 功能默认关闭需要在安装后的data/config.php中开启配置项定义于 config.default.php# Enable or not support of PubSubHubbub. # /!\ It should NOT be enabled if base_url is not reachable by an external server. pubsubhubbub_enabled false,同时必须正确设置base_urlconfig.default.php注释明确指出它用于构建绝对 URL例如 WebSub 回调# Specify address of the FreshRSS instance, # used when building absolute URLs, e.g. for WebSub. base_url https://freshrss.example.net/,在源码层面还有两个重要前置判断Feed.phpif ((Minz_Request::serverIsPublic($baseUrl) || self::isSameHost($this-hubUrl, $baseUrl)) $this-hubUrl ! $this-selfUrl ! is_dir(PSHB_PATH)) {即base_url必须对公网可达serverIsPublic或Hub 与实例同主机如本地开发环境 localhost 对 localhostisSameHost的兼容场景同时 feed 必须声明了hub链接且有self规范化 URL且data/PubSubHubbub目录存在。Web 安装向导会在服务器看起来具有公网地址时默认启用该功能。配套说明可见官方用户文档 docs/en/users/WebSub.md其中补充了 WebSub 的术语publisher / subscriber / hub、测试工具建议以及常见支持 WebSub 的平台WordPress、Blogger、Medium、Friendica 等。七、目录、日志与故障排查速查路径 / 配置说明data/PubSubHubbub/feeds/{sha1(url)}/!hub.jsonfeed 的 WebSub 订阅元数据hub、key、租约、错误标志data/PubSubHubbub/feeds/{sha1(url)}/{username}.txt用户订阅标记空文件data/PubSubHubbub/keys/{key}.txt订阅密钥 → canonical URL 映射回调鉴权用p/api/pshb.phpWebSub 回调端点订阅验证 内容推送分发data/users/_/log_pshb.txtWebSub 专用日志常量PSHB_LOG见 constants.phppubsubhubbub_enabled总开关默认false见 config.default.phpbase_url实例公网地址回调 URL 与serverIsPublic判断的依据常见故障快速定位!hub.json不存在feed 未声明link relhub或base_url非公网导致pubSubHubbubPrepare()直接跳过——检查 feed 源与系统配置error恒为true尚未收到第一次成功推送或出现「推送漏拉」被置位等待下一次成功推送或续租自愈lease_end已过期租约未及时续期pubSubHubbubPrepare()会在 23 小时窗口内自动续租可观察data/users/_/log_pshb.txt中的WebSub lease ends at ...警告回调返回410 Gonekeys/{key}.txt或feeds/{hash}/!hub.json已被清理如全部用户退订Hub 的陈旧推送会被安全拒绝并自我清理。结语data/PubSubHubbub/feeds/是 FreshRSS WebSub 实现的核心状态存储以sha1(canonical URL)组织 feed 目录用!hub.json保存订阅元数据用「每用户一个空文件」实现多用户共享订阅与推送分发。配合keys/目录的密钥映射和p/api/pshb.php的三层鉴权FreshRSS 仅凭纯文件系统即完成了订阅、续租、退订、推送与自愈的完整闭环。理解这一目录结构是运维 WebSub 实例、排查推送故障乃至为 FreshRSS 扩展 WebSub 相关功能如实现自定义 Hub 重定向的第一步。【免费下载链接】FreshRSSA free, self-hostable news aggregator…项目地址: https://gitcode.com/gh_mirrors/fr/FreshRSS创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表