internal/middleware: remove LegacyQuota
Change-Id: I4cdb402980ae6b70ddead303ff647959ea63f0a9
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/277814
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
diff --git a/go.mod b/go.mod
index f9829e5..8c954f1 100644
--- a/go.mod
+++ b/go.mod
@@ -21,7 +21,6 @@
github.com/go-redis/redis_rate/v9 v9.0.2
github.com/gogo/protobuf v1.3.0 // indirect
github.com/golang-migrate/migrate/v4 v4.6.2
- github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e
github.com/golang/protobuf v1.4.2
github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/google/go-cmp v0.5.2
diff --git a/internal/middleware/quota.go b/internal/middleware/quota.go
index 3afa3e2..a260182 100644
--- a/internal/middleware/quota.go
+++ b/internal/middleware/quota.go
@@ -13,18 +13,15 @@
"net"
"net/http"
"strings"
- "sync"
"time"
"github.com/go-redis/redis/v8"
rrate "github.com/go-redis/redis_rate/v9"
- "github.com/golang/groupcache/lru"
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
"go.opencensus.io/tag"
"golang.org/x/pkgsite/internal/config"
"golang.org/x/pkgsite/internal/log"
- "golang.org/x/time/rate"
)
var (
@@ -44,68 +41,6 @@
}
)
-// LegacyQuota implements a simple IP-based rate limiter. Each set of incoming IP
-// addresses with the same low-order byte gets qps requests per second, with the
-// given burst.
-// Information is kept in an LRU cache of size maxEntries.
-//
-// If a request is disallowed, a 429 (TooManyRequests) will be served.
-func LegacyQuota(settings config.QuotaSettings) Middleware {
- var mu sync.Mutex
- cache := lru.New(settings.MaxEntries)
-
- return func(h http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- authVal := r.Header.Get(config.BypassQuotaAuthHeader)
- for _, wantVal := range settings.AuthValues {
- if authVal == wantVal {
- recordQuotaMetric(r.Context(), "bypassed")
- log.Infof(r.Context(), "Quota: accepting %q", authVal)
- h.ServeHTTP(w, r)
- return
- }
- }
- header := r.Header.Get("X-Godoc-Forwarded-For")
- if header == "" {
- header = r.Header.Get("X-Forwarded-For")
- }
- key := ipKey(header)
- // key is empty if we couldn't parse an IP, or there is no IP.
- // Fail open in this case: allow serving.
- var limiter *rate.Limiter
- if key != "" {
- mu.Lock()
- if v, ok := cache.Get(key); ok {
- limiter = v.(*rate.Limiter)
- } else {
- limiter = rate.NewLimiter(rate.Limit(settings.QPS), settings.Burst)
- cache.Add(key, limiter)
- }
- mu.Unlock()
- }
- blocked := limiter != nil && !limiter.Allow()
- var mv string
- switch {
- case header == "":
- mv = "no header"
- case key == "":
- mv = "bad header"
- case blocked:
- mv = "blocked"
- default:
- mv = "allowed"
- }
- recordQuotaMetric(r.Context(), mv)
- if blocked && settings.RecordOnly != nil && !*settings.RecordOnly {
- const tmr = http.StatusTooManyRequests
- http.Error(w, http.StatusText(tmr), tmr)
- return
- }
- h.ServeHTTP(w, r)
- })
- }
-}
-
func recordQuotaMetric(ctx context.Context, blocked string) {
stats.RecordWithTags(ctx, []tag.Mutator{
tag.Upsert(keyQuotaBlocked, blocked),
diff --git a/internal/middleware/quota_test.go b/internal/middleware/quota_test.go
index a305df1..e910544 100644
--- a/internal/middleware/quota_test.go
+++ b/internal/middleware/quota_test.go
@@ -7,144 +7,14 @@
import (
"context"
"fmt"
- "net/http"
- "net/http/httptest"
"testing"
"time"
"github.com/alicebob/miniredis/v2"
"github.com/go-redis/redis/v8"
- "github.com/google/go-cmp/cmp"
"go.opencensus.io/stats/view"
- "golang.org/x/pkgsite/internal/config"
)
-func TestLegacyQuota(t *testing.T) {
- mw := LegacyQuota(config.QuotaSettings{QPS: 1, Burst: 2, MaxEntries: 1, RecordOnly: boolptr(false)})
- var npass int
- h := func(w http.ResponseWriter, r *http.Request) {
- npass++
- }
- ts := httptest.NewServer(mw(http.HandlerFunc(h)))
- defer ts.Close()
- c := ts.Client()
- view.Register(QuotaResultCount)
- defer view.Unregister(QuotaResultCount)
-
- check := func(msg string, nwant int) {
- npass = 0
- for i := 0; i < 5; i++ {
- req, err := http.NewRequest("GET", ts.URL, nil)
- if err != nil {
- t.Fatal(err)
- }
- req.Header.Add("X-Forwarded-For", "1.2.3.4, and more")
- res, err := c.Do(req)
- if err != nil {
- t.Fatalf("%s: %v", msg, err)
- }
- res.Body.Close()
- want := http.StatusOK
- if i >= nwant {
- want = http.StatusTooManyRequests
- }
- if got := res.StatusCode; got != want {
- t.Errorf("%s, #%d: got %d, want %d", msg, i, got, want)
- }
- }
- if npass != nwant {
- t.Errorf("%s: got %d requests to pass, want %d", msg, npass, nwant)
- }
- }
-
- // When making multiple requests in quick succession from the same IP,
- // only the first two get through; the rest are blocked.
- check("before", 2)
- // After a second (and a bit more), we should have one token back, meaning
- // we can serve one request.
- time.Sleep(1100 * time.Millisecond)
- check("after", 1)
-
- // Check the metric.
- got := collectViewData(t)
- want := map[bool]int{true: 7, false: 3} // only 3 requests of the ten we sent get through.
- if diff := cmp.Diff(want, got); diff != "" {
- t.Errorf("mismatch (-want +got):\n%s", diff)
- }
-}
-
-func TestLegacyQuotaRecordOnly(t *testing.T) {
- // Like TestQuota, but with in RecordOnly mode nothing is actually blocked.
- mw := LegacyQuota(config.QuotaSettings{QPS: 1, Burst: 2, MaxEntries: 1, RecordOnly: boolptr(true)})
- npass := 0
- h := func(w http.ResponseWriter, r *http.Request) {
- npass++
- }
- ts := httptest.NewServer(mw(http.HandlerFunc(h)))
- defer ts.Close()
- c := ts.Client()
- view.Register(QuotaResultCount)
- defer view.Unregister(QuotaResultCount)
-
- const nreq = 100
- for i := 0; i < nreq; i++ {
- req, err := http.NewRequest("GET", ts.URL, nil)
- if err != nil {
- t.Fatal(err)
- }
- req.Header.Add("X-Forwarded-For", "1.2.3.4, and more")
- res, err := c.Do(req)
- if err != nil {
- t.Fatal(err)
- }
- res.Body.Close()
- }
- if npass != nreq {
- t.Errorf("%d passed, want %d", npass, nreq)
- }
- got := collectViewData(t)
- want := map[bool]int{true: nreq - 2, false: 2} // record as if blocking occurred
- if diff := cmp.Diff(want, got); diff != "" {
- t.Errorf("mismatch (-want +got):\n%s", diff)
- }
-}
-
-func TestLegacyQuotaBadKey(t *testing.T) {
- // Verify that invalid IP addresses are not blocked.
- mw := LegacyQuota(config.QuotaSettings{QPS: 1, Burst: 2, MaxEntries: 1, RecordOnly: boolptr(true)})
- npass := 0
- h := func(w http.ResponseWriter, r *http.Request) {
- npass++
- }
- ts := httptest.NewServer(mw(http.HandlerFunc(h)))
- defer ts.Close()
- c := ts.Client()
- view.Register(QuotaResultCount)
- defer view.Unregister(QuotaResultCount)
-
- const nreq = 100
- for i := 0; i < nreq; i++ {
- req, err := http.NewRequest("GET", ts.URL, nil)
- if err != nil {
- t.Fatal(err)
- }
- req.Header.Add("X-Forwarded-For", "not.a.valid.ip, and more")
- res, err := c.Do(req)
- if err != nil {
- t.Fatal(err)
- }
- res.Body.Close()
- }
- if npass != nreq {
- t.Errorf("%d passed, want %d", npass, nreq)
- }
- got := collectViewData(t)
- want := map[bool]int{false: nreq} // no blocking occurred
- if diff := cmp.Diff(want, got); diff != "" {
- t.Errorf("mismatch (-want +got):\n%s", diff)
- }
-}
-
func collectViewData(t *testing.T) map[bool]int {
m := map[bool]int{}
rows, err := view.RetrieveData(QuotaResultCount.Name)