Skip to content

Latest commit

 

History

History
253 lines (189 loc) · 6.65 KB

v0.3.md

File metadata and controls

253 lines (189 loc) · 6.65 KB

HookHub 设计文档(草案 v0.3)

一句话定位:HookHub 是一个可插件化扩展的 Webhook 中间件——从任意服务接收 Webhook,按规则解析、转换并转发到任意目标。

Receive webhooks from anywhere, route them anywhere.

相对 v0.2:以当前仓库实现为准,对齐 Viper、Driver()、模板 { Event, Vars }、先 ACK 再异步处理等已落地决策。v0.2 保留为历史草案,不再回改。


0. 决策记录(相对 v0.2 / 相对实现)

决策 取值
投递语义 at-most-once:Verify/Parse 成功后立即 HTTP 200,再异步 Process;可丢,无持久化队列
配置加载 spf13/viper + 读入后 os.ExpandEnv;插件子配置 mapstructure + yaml tag
插件标识 接口方法 Driver() string;配置字段 driver;常量如 DriverVikunja / DriverNtfy
路径语义 POST /hook/{name},name = sources[].name(实例),不是 driver
Event.Source 仅表示实例名;由 handler 写入;Parse 不填
模板上下文 RenderContext{ Event, Vars };Vars 来自 sources[].vars
事件匹配 source 精确 + type 按 . 分段通配(* = 一段);无 CEL
包结构 扁平:cmd/hookhub、internal/{app,model,source/*,target/*};编排在 app
密钥 示例与本地配置用 ${ENV};config.yaml gitignore

1. 背景与目标

1.1 问题

各类 SaaS / 自建服务推送原始 JSON Webhook,结构不一,直接进 IM 可读性差;常需扇出到多个目标。

1.2 目标

  • 统一入口 /hook/{name} 多来源接入
  • 统一 Event → 配置化路由 → Message → Target
  • 编译期插件(driver 注册),单二进制
  • 尽力投递、可观测(结构化日志)

1.3 非目标(v0.3 仍不做)

  • Web UI、多租户、集群
  • CEL / when
  • 持久化队列、死信、跨进程去重
  • Query 参数鉴权(ntfy ?auth=)

2. 总体架构

POST /hook/{name}
       │
       ▼
  Source 实例(map[name])
  Verify → Parse → Event
  handler 写入 Event.Source = name
       │
       ├─ ?debug=1 → 日志预览,200,结束
       │
       ├─ 立即 200 ACK
       │
       └─ goroutine → Pipeline
              MatchingRoutes
              Template.Render({Event, Vars})
              Target.Send

三层解耦:Source(来源)→ Event → Target(目标)。Router/模板在 app + model。


3. 核心数据模型

3.1 Event

type Event struct {
    ID      string
    Source  string         // 实例名
    Type    string         // 事件种类,如 task.reminder.fired
    Time    time.Time
    Subject string
    Data    map[string]any
    Raw     []byte
    Headers http.Header
}

3.2 Message

type Message struct {
    Title, Body, Format, Priority string
    Extra map[string]string // Target 扩展,如 ntfy tags/click
}

3.3 路由与模板

type Route struct {
    Name    string
    Match   Match    // Source 实例名;Type 分段通配
    Actions []Action // Target 实例名 + Template
}

type RenderContext struct {
    Event *Event
    Vars  map[string]string
}

通配:段数相等;* 匹配恰好一段。空 Type = 该匹配下全部类型。


4. 插件接口

type Source interface {
    Driver() string
    Verify(req *http.Request, body []byte) error
    Parse(req *http.Request, body []byte) (*model.Event, error)
}

type Target interface {
    Driver() string
    Send(ctx context.Context, msg *model.Message, cfg map[string]any) error
}
  • 装配时校验 impl.Driver() == config.driver
  • Send 的 cfg 预留 Action 级覆盖;当前 ntfy 忽略

已实现驱动:vikunja、ntfy。


5. 配置

5.1 形状(列表实例)

见仓库根目录 config.example.yaml。

要点:

字段 含义
sources[].name 实例 → URL / Event.Source / match.source
sources[].driver 插件
sources[].vars 模板 .Vars(如 base_url)
sources[].config driver 配置(鉴权等)
targets[].name actions[].target 引用
routes 匹配 + 模板 + 扇出

密钥:${VIKUNJA_WEBHOOK_SECRET} 等,加载时 ExpandEnv。

5.2 模板

  • 事件字段:{{ .Event.Subject }}、{{ .Event.Data.task.title }}
  • 实例变量:{{ .Vars.base_url }}
  • missingkey=error:缺字段则该 Action 失败并跳过

6. 处理流水线(实现)

handleHook
  1. 按 name 取 Source
  2. 读 body(≤1MiB)
  3. Verify → 401
  4. Parse → 400;写入 Source/Time/Headers
  5. debug=1 → 截断预览日志,200,返回
  6. 200 + Flush
  7. go Process(timeout=30s)
       MatchingRoutes → Render → Send
       单 Action 失败只打日志,继续扇出
项 行为
投递保证 at-most-once
去重 / 队列 / 重试 未实现(后续)
debug 不转发;body 日志有长度上限

7. 目录结构

hookhub/
├── cmd/hookhub/main.go
├── config.example.yaml
├── README.md
├── docs/draft/v0.3.md          # 本文
└── internal/
    ├── app/                    # 配置、装配、HTTP、匹配表、pipeline
    ├── model/                  # Event、Message、Route、Match、Render
    ├── source/                 # 接口 + vikunja/
    └── target/                 # 接口 + ntfy/

8. 可靠性与安全(当前)

  • 验签:Vikunja HMAC / none(显式)
  • Body 上限 1MiB
  • text/template 无任意代码执行
  • ntfy Extra 头白名单 + 别名
  • 日志:debug body 截断;避免默认打印密钥

未做:持久化队列、SSRF 白名单(通用 http target 尚未实现)、Prometheus。


9. 技术选型

项 选型
语言 Go
配置 Viper + ExpandEnv + mapstructure
模板 text/template
匹配 自研分段通配
日志 log/slog
HTTP net/http ServeMux(Go 1.22+ 方法路由)

10. 里程碑状态

阶段 状态
P0 骨架 HTTP + 健康检查 已完成
P1 Vikunja + ntfy 已完成
P2 YAML 路由 + 模板 + Vars 已完成
P3 有界队列 / LRU 去重 / 重试 未做
P4 更多驱动 / CEL / 指标 按需

11. 后续方向

  • 内存有界队列 + 指数退避重试 + LRU 去重
  • 模板预编译
  • generic source / http target
  • CEL when、死信、Web UI、CloudEvents

文档状态:v0.3,对齐当前主干实现。运行说明见根目录 README.md。