插件扩展 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。
cron
POST /api/plugins/<cron_tag>/jobs/<job_name>/run
立即启动一个已配置的 cron job。job_name 是经过 URL 编码的 args.jobs[].name;手动触发使用与定时触发相同的 executor 列表和重叠保护,但不会改变 interval 或 cron 表达式任务的原定计划。
返回:
202 Accepted- 任务已被后台接受,响应中的
status为started、trigger为manual,并包含本轮run_id。202不代表 executor 链已经成功结束。
- 任务已被后台接受,响应中的
404 Not Found- 当前 cron runtime 中不存在该任务。
409 Conflict- 同一任务已有一轮正在执行;请求返回
cron_job_already_running,不会排队。
- 同一任务已有一轮正在执行;请求返回
503 Service Unavailable- 手动触发队列已满、TaskCenter 正在停止或对应任务已经不可用。
成功响应示例:
{
"ok": true,
"job": "refresh_sets",
"status": "started",
"trigger": "manual",
"run_id": 12
}
run_id 在单个已注册 job 的生命周期内从 1 递增,定时与手动运行共用同一序列;插件重新注册或进程重启后会重新计数。
GET /api/plugins/<cron_tag>/jobs/status
批量读取当前 cron runtime 中所有 job 的轻量状态,job 名称作为 jobs 对象的键:
{
"ok": true,
"jobs": {
"refresh_sets": {
"current_run": {
"run_id": 12,
"trigger": "manual",
"status": "running",
"started_at_ms": 1780000000000
},
"last_manual_run": null
},
"cleanup": {
"current_run": null,
"last_manual_run": {
"run_id": 8,
"status": "completed_with_errors",
"executor_error_count": 2,
"completed_at_ms": 1780000001000
}
}
}
}
current_run.trigger为manual、schedule或interval,status为pending或running。last_manual_run.status为completed、completed_with_errors、failed或cancelled;正常返回错误的 executor 会计入executor_error_count,但不会阻止后续 executor。- 时间字段都是 Unix 毫秒。接口只保留当前运行和最近一次手动结果,不提供历史、日志内容、结果详情或停止能力。
WebUI 会在当前配置版本已经运行时显示“手动运行”按钮,并每 2 秒批量同步真实状态。编辑配置或保存后尚未应用时按钮隐藏;重新打开详情不会重放已经结束的旧结果。
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。
download
GET /api/plugins/<tag>/downloads 返回正在运行的下载清单(index、url、path)和 running 状态。索引从 0 开始,对应运行中的配置。
POST /api/plugins/<tag>/download 使用 {"index": 0} 下载单项,使用 {} 下载全部。请求等待下载完成后返回 HTTP 200,响应示例:{"ok": true, "total": 1, "succeeded": 1, "failed": 0}。部分或全部下载失败时 ok 为 false,failed 包含超时项;后续项仍按声明顺序执行。详情记录在服务端日志和现有下载指标中。
输入格式错误返回 400,索引越界返回 404,已有下载运行时返回 409。手动请求与 pipeline/cron 下载共用运行锁,pipeline 执行会等待当前批次结束。接口需要同时启用 api 和 plugin-download,复用管理 API 认证。
接口只允许下载已配置的目标,不能提交 URL 或输出路径。下载沿用插件现有的文件替换行为,不会触发 provider reload 或外层 sequence 的其它步骤。
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 的 v2 查询历史(包括 steps 路径事件)和内存回放缓存。操作会先 flush 后台写入队列;cleared_records 表示本次删除的 v2 查询记录数。
升级前的 v1 历史不会被清空,仍占用磁盘空间。 其他 recorder 的数据也不受影响。如需手动清理 v1 历史,请仅删除对应的旧表,不要直接删除共用数据库。
返回:
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 和临时断连。