rate_limiter_test.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. package middleware
  2. import (
  3. "context"
  4. "net/http"
  5. "net/http/httptest"
  6. "testing"
  7. "time"
  8. ippkg "github.com/Wei-Shaw/sub2api/internal/pkg/ip"
  9. "github.com/gin-gonic/gin"
  10. "github.com/redis/go-redis/v9"
  11. "github.com/stretchr/testify/require"
  12. )
  13. func TestWindowTTLMillis(t *testing.T) {
  14. require.Equal(t, int64(1), windowTTLMillis(500*time.Microsecond))
  15. require.Equal(t, int64(1), windowTTLMillis(1500*time.Microsecond))
  16. require.Equal(t, int64(2), windowTTLMillis(2500*time.Microsecond))
  17. }
  18. func TestRateLimiterFailureModes(t *testing.T) {
  19. gin.SetMode(gin.TestMode)
  20. rdb := redis.NewClient(&redis.Options{
  21. Addr: "127.0.0.1:1",
  22. DialTimeout: 50 * time.Millisecond,
  23. ReadTimeout: 50 * time.Millisecond,
  24. WriteTimeout: 50 * time.Millisecond,
  25. })
  26. t.Cleanup(func() {
  27. _ = rdb.Close()
  28. })
  29. limiter := NewRateLimiter(rdb)
  30. failOpenRouter := gin.New()
  31. failOpenRouter.Use(limiter.Limit("test", 1, time.Second))
  32. failOpenRouter.GET("/test", func(c *gin.Context) {
  33. c.JSON(http.StatusOK, gin.H{"ok": true})
  34. })
  35. req := httptest.NewRequest(http.MethodGet, "/test", nil)
  36. req.RemoteAddr = "127.0.0.1:1234"
  37. recorder := httptest.NewRecorder()
  38. failOpenRouter.ServeHTTP(recorder, req)
  39. require.Equal(t, http.StatusOK, recorder.Code)
  40. failCloseRouter := gin.New()
  41. failCloseRouter.Use(limiter.LimitWithOptions("test", 1, time.Second, RateLimitOptions{
  42. FailureMode: RateLimitFailClose,
  43. }))
  44. failCloseRouter.GET("/test", func(c *gin.Context) {
  45. c.JSON(http.StatusOK, gin.H{"ok": true})
  46. })
  47. req = httptest.NewRequest(http.MethodGet, "/test", nil)
  48. req.RemoteAddr = "127.0.0.1:1234"
  49. recorder = httptest.NewRecorder()
  50. failCloseRouter.ServeHTTP(recorder, req)
  51. require.Equal(t, http.StatusTooManyRequests, recorder.Code)
  52. }
  53. func TestRateLimiterDifferentIPsIndependent(t *testing.T) {
  54. gin.SetMode(gin.TestMode)
  55. callCounts := make(map[string]int64)
  56. originalRun := rateLimitRun
  57. rateLimitRun = func(ctx context.Context, client *redis.Client, key string, windowMillis int64) (int64, bool, error) {
  58. callCounts[key]++
  59. return callCounts[key], false, nil
  60. }
  61. t.Cleanup(func() {
  62. rateLimitRun = originalRun
  63. })
  64. limiter := NewRateLimiter(redis.NewClient(&redis.Options{Addr: "127.0.0.1:1"}))
  65. router := gin.New()
  66. router.Use(limiter.Limit("api", 1, time.Second))
  67. router.GET("/test", func(c *gin.Context) {
  68. c.JSON(http.StatusOK, gin.H{"ok": true})
  69. })
  70. // 第一个 IP 的请求应通过
  71. req1 := httptest.NewRequest(http.MethodGet, "/test", nil)
  72. req1.RemoteAddr = "10.0.0.1:1234"
  73. rec1 := httptest.NewRecorder()
  74. router.ServeHTTP(rec1, req1)
  75. require.Equal(t, http.StatusOK, rec1.Code, "第一个 IP 的第一次请求应通过")
  76. // 第二个 IP 的请求应独立通过(不受第一个 IP 的计数影响)
  77. req2 := httptest.NewRequest(http.MethodGet, "/test", nil)
  78. req2.RemoteAddr = "10.0.0.2:5678"
  79. rec2 := httptest.NewRecorder()
  80. router.ServeHTTP(rec2, req2)
  81. require.Equal(t, http.StatusOK, rec2.Code, "第二个 IP 的第一次请求应独立通过")
  82. // 第一个 IP 的第二次请求应被限流
  83. req3 := httptest.NewRequest(http.MethodGet, "/test", nil)
  84. req3.RemoteAddr = "10.0.0.1:1234"
  85. rec3 := httptest.NewRecorder()
  86. router.ServeHTTP(rec3, req3)
  87. require.Equal(t, http.StatusTooManyRequests, rec3.Code, "第一个 IP 的第二次请求应被限流")
  88. }
  89. func TestRateLimiterAllow(t *testing.T) {
  90. originalRun := rateLimitRun
  91. var gotKey string
  92. count := int64(0)
  93. rateLimitRun = func(ctx context.Context, client *redis.Client, key string, windowMillis int64) (int64, bool, error) {
  94. gotKey = key
  95. count++
  96. return count, false, nil
  97. }
  98. t.Cleanup(func() {
  99. rateLimitRun = originalRun
  100. })
  101. // PTTL 走真实客户端(不可达地址)→ 失败后 RetryAfter 应回退为完整窗口
  102. limiter := NewRateLimiter(redis.NewClient(&redis.Options{
  103. Addr: "127.0.0.1:1",
  104. DialTimeout: 50 * time.Millisecond,
  105. ReadTimeout: 50 * time.Millisecond,
  106. WriteTimeout: 50 * time.Millisecond,
  107. }))
  108. res, err := limiter.Allow(context.Background(), "panel:global:user:42", 1, time.Minute)
  109. require.NoError(t, err)
  110. require.True(t, res.Allowed)
  111. require.Equal(t, int64(1), res.Count)
  112. require.Zero(t, res.RetryAfter)
  113. require.Equal(t, "rate_limit:panel:global:user:42", gotKey)
  114. res, err = limiter.Allow(context.Background(), "panel:global:user:42", 1, time.Minute)
  115. require.NoError(t, err)
  116. require.False(t, res.Allowed)
  117. require.Equal(t, int64(2), res.Count)
  118. require.Equal(t, time.Minute, res.RetryAfter)
  119. }
  120. func TestRateLimiterHonorsForwardedIPSnapshot(t *testing.T) {
  121. gin.SetMode(gin.TestMode)
  122. callCounts := make(map[string]int64)
  123. originalRun := rateLimitRun
  124. rateLimitRun = func(ctx context.Context, client *redis.Client, key string, windowMillis int64) (int64, bool, error) {
  125. callCounts[key]++
  126. return callCounts[key], false, nil
  127. }
  128. t.Cleanup(func() {
  129. rateLimitRun = originalRun
  130. })
  131. limiter := NewRateLimiter(redis.NewClient(&redis.Options{Addr: "127.0.0.1:1"}))
  132. router := gin.New()
  133. // 模拟 SessionBindingContext:开启转发 IP 兼容模式快照
  134. router.Use(func(c *gin.Context) {
  135. ippkg.SetForwardedIPSettings(c, true, nil)
  136. c.Next()
  137. })
  138. router.Use(limiter.Limit("fwd", 1, time.Second))
  139. router.GET("/test", func(c *gin.Context) {
  140. c.JSON(http.StatusOK, gin.H{"ok": true})
  141. })
  142. send := func(xff string) int {
  143. req := httptest.NewRequest(http.MethodGet, "/test", nil)
  144. // 所有请求都来自同一个反代地址
  145. req.RemoteAddr = "127.0.0.1:5678"
  146. req.Header.Set("X-Forwarded-For", xff)
  147. rec := httptest.NewRecorder()
  148. router.ServeHTTP(rec, req)
  149. return rec.Code
  150. }
  151. // 反代后两个不同的真实客户端应各自独立计数,不因共享代理地址被合并限流
  152. require.Equal(t, http.StatusOK, send("198.51.100.1"))
  153. require.Equal(t, http.StatusOK, send("198.51.100.2"))
  154. // 同一真实客户端第二次请求应被限流
  155. require.Equal(t, http.StatusTooManyRequests, send("198.51.100.1"))
  156. require.Contains(t, callCounts, "rate_limit:fwd:198.51.100.1")
  157. require.Contains(t, callCounts, "rate_limit:fwd:198.51.100.2")
  158. }
  159. func TestRateLimiterSuccessAndLimit(t *testing.T) {
  160. gin.SetMode(gin.TestMode)
  161. originalRun := rateLimitRun
  162. counts := []int64{1, 2}
  163. callIndex := 0
  164. rateLimitRun = func(ctx context.Context, client *redis.Client, key string, windowMillis int64) (int64, bool, error) {
  165. if callIndex >= len(counts) {
  166. return counts[len(counts)-1], false, nil
  167. }
  168. value := counts[callIndex]
  169. callIndex++
  170. return value, false, nil
  171. }
  172. t.Cleanup(func() {
  173. rateLimitRun = originalRun
  174. })
  175. limiter := NewRateLimiter(redis.NewClient(&redis.Options{Addr: "127.0.0.1:1"}))
  176. router := gin.New()
  177. router.Use(limiter.Limit("test", 1, time.Second))
  178. router.GET("/test", func(c *gin.Context) {
  179. c.JSON(http.StatusOK, gin.H{"ok": true})
  180. })
  181. req := httptest.NewRequest(http.MethodGet, "/test", nil)
  182. req.RemoteAddr = "127.0.0.1:1234"
  183. recorder := httptest.NewRecorder()
  184. router.ServeHTTP(recorder, req)
  185. require.Equal(t, http.StatusOK, recorder.Code)
  186. req = httptest.NewRequest(http.MethodGet, "/test", nil)
  187. req.RemoteAddr = "127.0.0.1:1234"
  188. recorder = httptest.NewRecorder()
  189. router.ServeHTTP(recorder, req)
  190. require.Equal(t, http.StatusTooManyRequests, recorder.Code)
  191. }