// Package middleware 提供 Guardrail 与限流等接入层中间件。 package middleware import ( "bytes" "io" "log" "net/http" "strings" "time" "github.com/gin-gonic/gin" "github.com/sundynix/sundynix-gateway/internal/guardrail" "github.com/sundynix/sundynix-gateway/internal/store" ) // Guardrail 实现 Harness 输入护栏:拦截提示词注入 / 超大请求体。 // 只检查带 JSON 体的写请求(POST/PUT);文件上传(multipart)与 GET/SSE 不经此。 // 输出护栏不在此做 —— Token 流为 SSE 实时流,网关缓冲会破坏流式,输出过滤应在 // dispatcher 的 token 发射层(见 PROGRESS 路线图)。 func Guardrail() gin.HandlerFunc { return func(c *gin.Context) { if m := c.Request.Method; (m == http.MethodPost || m == http.MethodPut) && strings.HasPrefix(c.GetHeader("Content-Type"), "application/json") { // 限读上限 + 1 字节以判定"过大";命中拦截则后续 handler 不执行。 body, _ := io.ReadAll(io.LimitReader(c.Request.Body, guardrail.MaxJSONBytes+1)) if reason, blocked := guardrail.Inspect(body); blocked { log.Printf("[guardrail] 拦截 %s %s:%s", c.Request.Method, c.Request.URL.Path, reason) c.AbortWithStatusJSON(http.StatusUnprocessableEntity, gin.H{"error": "输入护栏拦截:" + reason}) return } c.Request.Body = io.NopCloser(bytes.NewReader(body)) // 还原请求体供后续 handler 读取 } c.Next() } } // RateLimit 基于 Redis 的会话级限流(按客户端 IP,每分钟上限)。 // Redis 降级时 Allow 始终放行,不阻断业务。 func RateLimit(cache *store.Redis) gin.HandlerFunc { const perMinute = 120 return func(c *gin.Context) { ok, _ := cache.Allow(c.Request.Context(), c.ClientIP(), perMinute, time.Minute) if !ok { c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{"error": "rate limit exceeded"}) return } c.Next() } }