diff --git a/CHANGELOG.md b/CHANGELOG.md index e568896..80fcb42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # 更新日志 +24w24a +--- +- PRE-RELEASE: 此版本是v1.7.5的预发布版本,请勿在生产环境中使用 +- ADD: `Rate`模块加入`IP`速率限制,可限制单个IP的请求速率 +- CHANGE: 处理积攒的依赖库更新,更新如下依赖库: +- **github.com/gabriel-vasile/mimetype**: 从 v1.4.6 升级到 v1.4.7 +- **github.com/go-playground/validator/v10**: 从 v10.22.1 升级到 v10.23.0 +- **github.com/klauspost/cpuid/v2**: 从 v2.2.8 升级到 v2.2.9 +- **github.com/onsi/ginkgo/v2**: 从 v2.21.0 升级到 v2.22.0 +- **golang.org/x/arch**: 从 v0.11.0 升级到 v0.12.0 +- **golang.org/x/crypto**: 从 v0.28.0 升级到 v0.29.0 +- **golang.org/x/exp**: 从 v0.0.0-20241009180824-f66d83c29e7c 升级到 v0.0.0-20241108190413-2d47ceb2692f +- **golang.org/x/mod**: 从 v0.21.0 升级到 v0.22.0 +- **golang.org/x/net**: 从 v0.30.0 升级到 v0.31.0 +- **golang.org/x/sync**: 从 v0.8.0 升级到 v0.9.0 +- **golang.org/x/sys**: 从 v0.26.0 升级到 v0.27.0 +- **golang.org/x/text**: 从 v0.19.0 升级到 v0.20.0 +- **golang.org/x/tools**: 从 v0.26.0 升级到 v0.27.0 +- **google.golang.org/protobuf**: 从 v1.35.1 升级到 v1.35.2 + v1.7.4 --- - CHANGE: 对二进制文件部署脚本进行优化 diff --git a/config/config.go b/config/config.go index a9eaa58..3a27dd0 100644 --- a/config/config.go +++ b/config/config.go @@ -53,9 +53,10 @@ type WhitelistConfig struct { } type RateLimitConfig struct { - Enabled bool `toml:"enabled"` - RatePerMinute int `toml:"ratePerMinute"` - Burst int `toml:"burst"` + Enabled bool `toml:"enabled"` + RateMethod string `toml:"rateMethod"` + RatePerMinute int `toml:"ratePerMinute"` + Burst int `toml:"burst"` } // LoadConfig 从 TOML 配置文件加载配置 diff --git a/config/config.toml b/config/config.toml index a7cccbb..61cce03 100644 --- a/config/config.toml +++ b/config/config.toml @@ -30,5 +30,6 @@ whitelistFile = "/data/ghproxy/config/whitelist.json" [rateLimit] enabled = false +rateMrthod = "total" # "ip" or "total" ratePerMinute = 180 burst = 5 diff --git a/deploy/config.toml b/deploy/config.toml index 06c8678..4789d92 100644 --- a/deploy/config.toml +++ b/deploy/config.toml @@ -30,5 +30,6 @@ whitelistFile = "/usr/local/ghproxy/config/whitelist.json" [rateLimit] enabled = false +rateMrthod = "total" # "ip" or "total" ratePerMinute = 180 burst = 5 diff --git a/docker/dockerfile/nocache/config.toml b/docker/dockerfile/nocache/config.toml index 68af852..ed8a857 100644 --- a/docker/dockerfile/nocache/config.toml +++ b/docker/dockerfile/nocache/config.toml @@ -30,5 +30,6 @@ whitelistFile = "/data/ghproxy/config/whitelist.json" [rateLimit] enabled = false +rateMrthod = "total" # "ip" or "total" ratePerMinute = 180 burst = 5 diff --git a/go.mod b/go.mod index 47695a0..243b9d6 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/go-playground/validator/v10 v10.23.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/goccy/go-json v0.10.3 // indirect - github.com/google/pprof v0.0.0-20241101162523-b92577c0c142 // indirect + github.com/google/pprof v0.0.0-20241122213907-cbe949e5a41b // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/go.sum b/go.sum index 4fc4715..3c5fdb5 100644 --- a/go.sum +++ b/go.sum @@ -46,6 +46,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20241101162523-b92577c0c142 h1:sAGdeJj0bnMgUNVeUpp6AYlVdCt3/GdI3pGRqsNSQLs= github.com/google/pprof v0.0.0-20241101162523-b92577c0c142/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20241122213907-cbe949e5a41b h1:SXO0REt4iu865upYCk8aKBBJQ4BqoE0ReP23ClMu60s= +github.com/google/pprof v0.0.0-20241122213907-cbe949e5a41b/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= diff --git a/main.go b/main.go index a71b119..3fd40e6 100644 --- a/main.go +++ b/main.go @@ -22,8 +22,9 @@ var ( router *gin.Engine configfile = "/data/ghproxy/config/config.toml" cfgfile string - limiter *rate.RateLimiter version string + limiter *rate.RateLimiter + iplimiter *rate.IPRateLimiter ) var ( @@ -68,7 +69,13 @@ func setupApi(cfg *config.Config, router *gin.Engine, version string) { func setupRateLimit(cfg *config.Config) { if cfg.RateLimit.Enabled { - limiter = rate.New(cfg.RateLimit.RatePerMinute, cfg.RateLimit.Burst, 1*time.Minute) + if cfg.RateLimit.RateMethod == "ip" { + iplimiter = rate.NewIPRateLimiter(cfg.RateLimit.RatePerMinute, cfg.RateLimit.Burst, 1*time.Minute) + } else if cfg.RateLimit.RateMethod == "total" { + limiter = rate.New(cfg.RateLimit.RatePerMinute, cfg.RateLimit.Burst, 1*time.Minute) + } else { + logError("Invalid RateLimit Method: %s", cfg.RateLimit.RateMethod) + } logInfo("Rate Limit Loaded") } } @@ -106,7 +113,7 @@ func init() { } router.NoRoute(func(c *gin.Context) { - proxy.NoRouteHandler(cfg, limiter)(c) + proxy.NoRouteHandler(cfg, limiter, iplimiter)(c) }) } diff --git a/proxy/proxy.go b/proxy/proxy.go index 0eb3b50..8e77941 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -33,12 +33,24 @@ var exps = []*regexp.Regexp{ regexp.MustCompile(`^(?:https?://)?gist\.github(?:usercontent|)\.com/([^/]+)/.+?/.+`), } -func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter) gin.HandlerFunc { +func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *rate.IPRateLimiter) gin.HandlerFunc { return func(c *gin.Context) { // 限制访问频率 if cfg.RateLimit.Enabled { - logInfo("Rate_Limit Enabled") - if !limiter.Allow() { + + var allowed bool + + switch cfg.RateLimit.RateMethod { + case "ip": + allowed = iplimiter.Allow(c.ClientIP()) + case "total": + allowed = limiter.Allow() + default: + logWarning("Invalid RateLimit Method") + return + } + + if !allowed { c.JSON(http.StatusTooManyRequests, gin.H{"error": "Too Many Requests"}) logWarning("%s %s %s %s %s 429-TooManyRequests", c.ClientIP(), c.Request.Method, c.Request.URL.RequestURI(), c.Request.Header.Get("User-Agent"), c.Request.Proto) return diff --git a/rate/rate.go b/rate/rate.go index 707e183..1b06857 100644 --- a/rate/rate.go +++ b/rate/rate.go @@ -1,15 +1,33 @@ package rate import ( + "ghproxy/logger" "time" "golang.org/x/time/rate" ) +// 日志输出 +var ( + logw = logger.Logw + logInfo = logger.LogInfo + logWarning = logger.LogWarning + logError = logger.LogError +) + +// 总体限流器 type RateLimiter struct { limiter *rate.Limiter } +// 基于IP的限流器 +type IPRateLimiter struct { + limiters map[string]*RateLimiter + limit int + burst int + duration time.Duration +} + func New(limit int, burst int, duration time.Duration) *RateLimiter { return &RateLimiter{ limiter: rate.NewLimiter(rate.Limit(float64(limit)/duration.Seconds()), burst), @@ -19,3 +37,27 @@ func New(limit int, burst int, duration time.Duration) *RateLimiter { func (rl *RateLimiter) Allow() bool { return rl.limiter.Allow() } + +func NewIPRateLimiter(limit int, burst int, duration time.Duration) *IPRateLimiter { + return &IPRateLimiter{ + limiters: make(map[string]*RateLimiter), + limit: limit, + burst: burst, + duration: duration, + } +} + +func (rl *IPRateLimiter) Allow(ip string) bool { + if ip == "" { + logWarning("empty ip") + return false + } + + limiter, ok := rl.limiters[ip] + if !ok { + // 创建新的 RateLimiter 并存储 + limiter = New(rl.limit, rl.burst, rl.duration) + rl.limiters[ip] = limiter + } + return limiter.Allow() +}