diff --git a/CHANGELOG.md b/CHANGELOG.md index a6b33b0..05db439 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # 更新日志 +24w21a +--- +- PRE-RELEASE: 此版本是v1.7.0的预发布版本,请勿在生产环境中使用 +- ADD: 尝试加入程序内置速率限制 +- CHANGE: 更新相关依赖库 + v1.6.2 --- - CHANGE: 优化前端界面,优化部分样式 diff --git a/DEV-VERSION b/DEV-VERSION index 27812aa..b9b04a9 100644 --- a/DEV-VERSION +++ b/DEV-VERSION @@ -1 +1 @@ -24w20b \ No newline at end of file +24w21a \ No newline at end of file diff --git a/config/config.go b/config/config.go index 4d50310..033a7ea 100644 --- a/config/config.go +++ b/config/config.go @@ -12,6 +12,7 @@ type Config struct { Auth AuthConfig Blacklist BlacklistConfig Whitelist WhitelistConfig + RateLimit RateLimitConfig } type ServerConfig struct { @@ -49,6 +50,12 @@ type WhitelistConfig struct { WhitelistFile string `toml:"whitelistFile"` } +type RateLimitConfig struct { + Enabled bool `toml:"enabled"` + RatePerMinute int `toml:"ratePerMinute"` + Burst int `toml:"burst"` +} + // LoadConfig 从 TOML 配置文件加载配置 func LoadConfig(filePath string) (*Config, error) { var config Config diff --git a/config/config.toml b/config/config.toml index 7dc645b..8481dc1 100644 --- a/config/config.toml +++ b/config/config.toml @@ -25,3 +25,8 @@ enabled = false [whitelist] enabled = false whitelistFile = "/data/ghproxy/config/whitelist.json" + +[rateLimit] +enabled = false +ratePerMinute = 100 +burst = 10 diff --git a/go.mod b/go.mod index 57c4eaa..b57e697 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/go-playground/validator/v10 v10.22.1 // 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-20241023014458-598669927662 // indirect + github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // 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 @@ -32,7 +32,7 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/onsi/ginkgo/v2 v2.20.2 // indirect + github.com/onsi/ginkgo/v2 v2.21.0 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.48.1 // indirect @@ -48,6 +48,7 @@ require ( golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect + golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.26.0 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index adb96fd..49b0619 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,8 @@ github.com/google/pprof v0.0.0-20241017200806-017d972448fc h1:NGyrhhFhwvRAZg02jn github.com/google/pprof v0.0.0-20241017200806-017d972448fc/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/pprof v0.0.0-20241023014458-598669927662 h1:SKMkD83p7FwUqKmBsPdLHF5dNyxq3jOWwu9w9UyH5vA= github.com/google/pprof v0.0.0-20241023014458-598669927662/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/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= @@ -76,8 +78,11 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= +github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -132,6 +137,8 @@ golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= diff --git a/main.go b/main.go index 7dca0a0..0874c21 100644 --- a/main.go +++ b/main.go @@ -5,12 +5,14 @@ import ( "fmt" "log" "net/http" + "time" "ghproxy/api" "ghproxy/auth" "ghproxy/config" "ghproxy/logger" "ghproxy/proxy" + "ghproxy/rate" "github.com/gin-gonic/gin" ) @@ -20,6 +22,7 @@ var ( router *gin.Engine configfile = "/data/ghproxy/config/config.toml" cfgfile string + limiter *rate.RateLimiter ) var ( @@ -62,6 +65,13 @@ func setupApi(cfg *config.Config, router *gin.Engine) { api.InitHandleRouter(cfg, router) } +func setupRateLimit(cfg *config.Config) { + if cfg.RateLimit.Enabled { + limiter = rate.New(cfg.RateLimit.RatePerMinute, cfg.RateLimit.Burst, 1*time.Minute) + logInfo("Rate Limit Loaded") + } +} + func init() { readFlag() flag.Parse() @@ -92,7 +102,7 @@ func init() { } router.NoRoute(func(c *gin.Context) { - proxy.NoRouteHandler(cfg)(c) + proxy.NoRouteHandler(cfg, limiter)(c) }) } diff --git a/proxy/proxy.go b/proxy/proxy.go index 91e40cb..be1223f 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -11,6 +11,7 @@ import ( "ghproxy/auth" "ghproxy/config" "ghproxy/logger" + "ghproxy/rate" "github.com/gin-gonic/gin" "github.com/imroc/req/v3" @@ -32,8 +33,17 @@ var exps = []*regexp.Regexp{ regexp.MustCompile(`^(?:https?://)?gist\.github(?:usercontent|)\.com/([^/]+)/.+?/.+`), } -func NoRouteHandler(cfg *config.Config) gin.HandlerFunc { +func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter) gin.HandlerFunc { return func(c *gin.Context) { + // 限制访问频率 + if cfg.RateLimit.Enabled { + if !limiter.Allow() { + 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 + } + } + rawPath := strings.TrimPrefix(c.Request.URL.RequestURI(), "/") re := regexp.MustCompile(`^(http:|https:)?/?/?(.*)`) matches := re.FindStringSubmatch(rawPath) diff --git a/rate/rate.go b/rate/rate.go new file mode 100644 index 0000000..707e183 --- /dev/null +++ b/rate/rate.go @@ -0,0 +1,21 @@ +package rate + +import ( + "time" + + "golang.org/x/time/rate" +) + +type RateLimiter struct { + limiter *rate.Limiter +} + +func New(limit int, burst int, duration time.Duration) *RateLimiter { + return &RateLimiter{ + limiter: rate.NewLimiter(rate.Limit(float64(limit)/duration.Seconds()), burst), + } +} + +func (rl *RateLimiter) Allow() bool { + return rl.limiter.Allow() +}