插件扩展 API
插件可以在统一 tag 路径下注册管理和观测接口。路由是否存在取决于当前配置和二进制编译能力。
插件扩展 API
统一格式
/api/plugins/<plugin_tag>/<route>
说明:
- 也有少量插件会把主资源绑定到前缀路由下,例如
query_recorder的/api/plugins/<tag>/records/<id>。
cache
GET /api/plugins/<cache_tag>/entries
分页读取缓存项。
查询参数:
limit:每页数量,默认100,最大500。cursor:分页游标。qname:按缓存键中的查询域名做大小写不敏感的包含筛选。
GET /api/plugins/<cache_tag>/flush
清空缓存。
GET /api/plugins/<cache_tag>/dump
导出缓存 dump。
POST /api/plugins/<cache_tag>/load_dump
导入缓存 dump。
matcher
配置在 plugins: 中、具有稳定 tag 的 matcher 都会注册以下运行时控制接口:
GET /api/plugins/<matcher_tag>/status
POST /api/plugins/<matcher_tag>/mode
两个接口返回相同的状态结构:
{
"ok": true,
"matcher": "match_cn",
"mode": "normal"
}
设置模式时提交 JSON 请求体:
{ "mode": "always_false" }
mode 支持 normal、always_false 和 always_true,重复设置同一模式是幂等操作。两个固定模式都会跳过内部 matcher,并固定 matcher 的基础布尔值;每个引用随后仍独立应用取反。因此 always_false 下 $match_cn 不命中而 !$match_cn 命中,always_true 下结果相反。缺少或不支持的模式返回 400 invalid_matcher_runtime_mode。
模式只属于当前 runtime,不写入 YAML;应用级 reload 或进程重启后恢复为 normal。sequence 和 any_match 内的 quick-setup matcher 使用内部 qs.match... tag,不注册控制接口。如需运行时控制,应先将其提取为 plugins: 中的独立 matcher 并通过 $tag 引用。
WebUI 对 always_false 与 always_true 都要求二次确认;恢复 normal 不需要确认。旧的 /enable、/disable 接口和 enabled 响应字段已经移除,API 客户端必须迁移到 /mode。
provider
POST /api/plugins/<provider_tag>/reload
作用:
- 使用 provider 启动时的同一份配置,定向刷新该 provider 的内部数据快照。
- 不会重建其它插件,也不会修改 provider tag、依赖关系或配置拓扑。
返回:
200 OK- provider 已成功 reload。
400 Bad Request- provider 在 reload 过程中返回错误。
404 Not Found- 对应 tag 不是当前 runtime 中已加载的 provider,因此没有注册该路由。
409 Conflict- 同一个 provider 已有 reload 正在执行;新请求会立即返回
provider_reload_busy,不会排队。
- 同一个 provider 已有 reload 正在执行;新请求会立即返回
适用场景:
- 规则文件下载完成后,只刷新受影响的
domain_set、ip_set、geosite、geoip、adguard_ruleprovider。 - 需要避免应用级全量
POST /api/reload对其它插件造成重建影响。
注意:
- 如果变更涉及
config.yaml、provider 依赖拓扑、插件列表或其它非 provider 结构,仍然需要使用POST /api/reload。
WebUI 在所有已应用的 provider 卡片和详情页中提供“重新加载数据”操作。该操作不会弹出配置变更确认;执行期间会阻止同一 provider 的重复请求,并展示成功、失败或 busy 结果。
reverse_lookup
GET /api/plugins/<tag>?ip=<ip_addr>
按 IP 查询缓存中的域名。
示例:
GET /api/plugins/reverse_lookup_main?ip=8.8.8.8
返回:
- 命中:域名文本,通常为 fully-qualified domain name。
- 未命中:空响应体。
- 参数错误:
400 Bad Request。
query_recorder
GET /api/plugins/<tag>/records
按 created_at_ms 倒序返回 recorder 主表中的记录列表,不包含 steps。
查询参数:
cursor=<created_at_ms>:<id>- 用于继续向后翻页。
limit=<n>- 默认
100,最大500。
- 默认
since_ms=<unix_ms>- 仅返回大于等于该时间的记录。
until_ms=<unix_ms>- 仅返回小于等于该时间的记录。
qname=<text>- 按请求问题名做大小写不敏感的包含匹配。
client_ip=<text>- 按客户端 IP 字符串做大小写不敏感的包含匹配,可输入 IPv4/IPv6 片段。
qtype=<type>- 按请求问题类型精确匹配。
rcode=<rcode>- 按响应码精确匹配。
status=all|error|has_response|no_response- 按记录状态过滤。
client_ip 是 DNS 传输层看到的对端地址。若记录列表或 /stats/top_clients 全部显示 127.0.0.1,通常说明请求先经过了本机转发器,例如 systemd-resolved、dnsmasq、AdGuardHome、dae 或 clash;请检查部署链路,让客户端直接访问 OxiDNS,或在 HTTP/DoH 反向代理场景配置可信的 src_ip_header。
返回:
200 OK- JSON,形如:
{
"ok": true,
"next_cursor": "1713510000123:42",
"records": [
{
"id": 42,
"created_at_ms": 1713510000123,
"elapsed_ms": 12,
"request_id": 1234,
"client_ip": "192.0.2.10",
"questions_json": [
{ "name": "www.example.com.", "qtype": "A", "qclass": "IN" }
],
"req_rd": true,
"req_cd": false,
"req_ad": false,
"req_opcode": "Query",
"req_edns_json": null,
"error": null,
"has_response": true,
"rcode": "NoError",
"resp_aa": false,
"resp_tc": false,
"resp_ra": true,
"resp_ad": false,
"resp_cd": false,
"answer_count": 1,
"authority_count": 0,
"additional_count": 0,
"answers_json": [
{
"name": "www.example.com.",
"class": "IN",
"ttl": 300,
"rr_type": "A",
"payload_kind": "A",
"payload_text": "192.0.2.1",
"payload": { "ip": "192.0.2.1" }
}
],
"authorities_json": [],
"additionals_json": [],
"signature_json": [],
"resp_edns_json": null
}
]
}
GET /api/plugins/<tag>/records/<id>
返回单条完整记录,并附带 steps 路径事件数组。
返回:
200 OK- JSON,包含
record对象;其中record.record为主表字段,record.steps为路径事件。
- JSON,包含
404 Not Found- 记录不存在。
DELETE /api/plugins/<tag>/records
清空当前 recorder 的所有历史查询记录和 steps 路径事件。清空操作会先 flush 后台写入队列,随后删除 SQLite 记录表中的所有行,并清空内存 tail。
返回:
200 OK- JSON,形如:
{
"ok": true,
"cleared_records": 128
}
GET /api/plugins/<tag>/stats/plugins
按路径事件聚合插件命中情况。
查询参数:
since_ms=<unix_ms>until_ms=<unix_ms>kind=matcher|executor|builtin|all- 同
/records支持qname、client_ip、qtype、rcode、status过滤。
返回字段:
kindtagcheckedmatchedexecutedquery_totalquery_share
GET /api/plugins/<tag>/stats/top_clients
按客户端 IP 聚合查询次数。
查询参数:
limit=<n>- 返回桶数,默认
20。后端不再强制200上限;过大的值会增加 SQLite 排序和响应体成本。
- 返回桶数,默认
- 同
/records支持since_ms、until_ms、qname、client_ip、qtype、rcode、status、matcher_tag过滤。
返回字段:
sample_sizerows[].keyrows[].countrows[].share
GET /api/plugins/<tag>/stats/top_qnames
按查询问题名聚合查询次数。查询参数和返回字段同 /stats/top_clients。
GET /api/plugins/<tag>/stats/qtype
按 QTYPE 聚合分布。支持 /records 的时间范围与过滤参数,返回 sample_size 和 rows[].key/count/share。
GET /api/plugins/<tag>/stats/rcode
按响应码或特殊状态桶聚合分布。支持 /records 的时间范围与过滤参数,返回 sample_size 和 rows[].key/count/share。
GET /api/plugins/<tag>/stats/latency
返回延迟摘要、直方图和慢查询排行。
查询参数:
slow_limit=<n>或limit=<n>- 慢查询排行返回数量,默认
20。后端不再强制200上限。
- 慢查询排行返回数量,默认
- 同
/records支持时间范围与过滤参数。
GET /api/plugins/<tag>/stats/timeseries
按时间桶聚合查询趋势。
查询参数:
bucket=minute|hourbuckets=<n>- 返回桶数,默认
60,最大720。
- 返回桶数,默认
- 同
/records支持时间范围与过滤参数。
GET /api/plugins/<tag>/stream
通过 SSE 推送新写入的完整记录。
查询参数:
tail=<n>- 先回放最近
n条内存 tail,再持续推送新记录。
- 先回放最近
说明:
event: record的data为完整RecordDetailJSON。- 会定期发送 heartbeat 注释帧以保持长连接。
- 客户端应使用
Accept: text/event-stream,并能容忍 heartbeat、错误事件、空 payload 和临时断连。