// 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 h.server.proxiesMu.RLock() for _, p := range h.server.proxies { if pcache := p.GetCache(); pcache != nil { _ = pcache.Delete(hashKey) deleted++ } } h.server.proxiesMu.RUnlock() return deleted } // purgeByPattern 按通配符模式清理缓存。 func (h *PurgeHandler) purgeByPattern(pattern string, method string) int { if h.server == nil { return 0 } deleted := 0 h.server.proxiesMu.RLock() for _, p := range h.server.proxies { if pcache := p.GetCache(); pcache != nil { deleted += pcache.DeleteByPatternWithMethod(pattern, method) } } h.server.proxiesMu.RUnlock() return deleted }