153 lines
3.6 KiB
Go
153 lines
3.6 KiB
Go
// Package server 提供 HTTP 服务器核心功能。
|
||
//
|
||
// 该文件实现缓存清理 API 处理器,支持主动清理代理缓存。
|
||
package server
|
||
|
||
import (
|
||
"encoding/json"
|
||
"net"
|
||
|
||
"github.com/valyala/fasthttp"
|
||
"rua.plus/lolly/internal/cache"
|
||
"rua.plus/lolly/internal/config"
|
||
"rua.plus/lolly/internal/utils"
|
||
)
|
||
|
||
// PurgeHandler 缓存清理 API 处理器。
|
||
//
|
||
// 持有 Server 引用以访问所有代理实例的缓存。
|
||
// 支持 IP 白名单和 Token 认证保护。
|
||
//
|
||
// 注意事项:
|
||
// - 仅处理 POST 请求
|
||
// - 支持按路径和按模式两种清理方式
|
||
// - method 参数支持指定 HTTP 方法(默认 GET)
|
||
type PurgeHandler struct {
|
||
server *Server
|
||
auth config.CacheAPIAuthConfig
|
||
path string
|
||
allowed []net.IPNet
|
||
}
|
||
|
||
// NewPurgeHandler 创建缓存清理 API 处理器。
|
||
//
|
||
// 解析 IP 白名单配置,支持 CIDR 格式和单个 IP。
|
||
// localhost 特殊处理为 127.0.0.1 和 ::1。
|
||
//
|
||
// 参数:
|
||
// - server: Server 实例,用于访问代理缓存
|
||
// - cfg: CacheAPI 配置
|
||
//
|
||
// 返回值:
|
||
// - *PurgeHandler: 配置好的处理器
|
||
// - error: IP 解析失败时返回非 nil 错误
|
||
func NewPurgeHandler(server *Server, cfg *config.CacheAPIConfig) (*PurgeHandler, error) {
|
||
h := &PurgeHandler{
|
||
server: server,
|
||
path: cfg.Path,
|
||
auth: cfg.Auth,
|
||
}
|
||
|
||
// 默认路径
|
||
if h.path == "" {
|
||
h.path = "/_cache/purge"
|
||
}
|
||
|
||
// 解析允许的 IP 列表
|
||
allowed, err := utils.ParseIPAllowList(cfg.Allow)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
h.allowed = allowed
|
||
|
||
return h, nil
|
||
}
|
||
|
||
// Path 返回 API 端点路径。
|
||
func (h *PurgeHandler) Path() string {
|
||
return h.path
|
||
}
|
||
|
||
// ServeHTTP 处理缓存清理请求。
|
||
//
|
||
// 仅处理 POST 请求,支持精确路径和通配符模式清理。
|
||
// 返回 JSON 格式的响应。
|
||
func (h *PurgeHandler) ServeHTTP(ctx *fasthttp.RequestCtx) {
|
||
// 仅允许 POST 方法
|
||
if string(ctx.Method()) != "POST" {
|
||
utils.SendJSONError(ctx, fasthttp.StatusMethodNotAllowed, "method not allowed")
|
||
return
|
||
}
|
||
|
||
// 检查 IP 访问权限
|
||
if !utils.CheckIPAccess(ctx, h.allowed) {
|
||
utils.SendJSONError(ctx, fasthttp.StatusForbidden, "forbidden")
|
||
return
|
||
}
|
||
|
||
// 检查认证
|
||
if !utils.CheckTokenAuth(ctx, h.auth) {
|
||
utils.SendJSONError(ctx, fasthttp.StatusUnauthorized, "unauthorized")
|
||
return
|
||
}
|
||
|
||
// 解析请求体
|
||
var req cache.PurgeRequest
|
||
if err := json.Unmarshal(ctx.PostBody(), &req); err != nil {
|
||
utils.SendJSONError(ctx, fasthttp.StatusBadRequest, "invalid request body")
|
||
return
|
||
}
|
||
|
||
// 执行清理
|
||
deleted := 0
|
||
if req.Path != "" {
|
||
deleted = h.purgeByPath(req.Path, req.Method)
|
||
} else if req.Pattern != "" {
|
||
deleted = h.purgeByPattern(req.Pattern, req.Method)
|
||
} else {
|
||
utils.SendJSONError(ctx, fasthttp.StatusBadRequest, "missing path or pattern")
|
||
return
|
||
}
|
||
|
||
// 返回响应
|
||
ctx.SetContentType("application/json; charset=utf-8")
|
||
ctx.SetStatusCode(fasthttp.StatusOK)
|
||
_ = json.NewEncoder(ctx).Encode(cache.PurgeResponse{Deleted: deleted})
|
||
}
|
||
|
||
// purgeByPath 按精确路径清理缓存。
|
||
func (h *PurgeHandler) purgeByPath(path string, method string) int {
|
||
if h.server == nil {
|
||
return 0
|
||
}
|
||
|
||
hashKey := cache.HashPathWithMethod(path, method)
|
||
deleted := 0
|
||
|
||
for _, p := range h.server.proxies {
|
||
if pcache := p.GetCache(); pcache != nil {
|
||
_ = pcache.Delete(hashKey)
|
||
deleted++
|
||
}
|
||
}
|
||
|
||
return deleted
|
||
}
|
||
|
||
// purgeByPattern 按通配符模式清理缓存。
|
||
func (h *PurgeHandler) purgeByPattern(pattern string, method string) int {
|
||
if h.server == nil {
|
||
return 0
|
||
}
|
||
|
||
deleted := 0
|
||
|
||
for _, p := range h.server.proxies {
|
||
if pcache := p.GetCache(); pcache != nil {
|
||
deleted += pcache.DeleteByPatternWithMethod(pattern, method)
|
||
}
|
||
}
|
||
|
||
return deleted
|
||
}
|