diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2023-11-02 19:06:05 +0100 |
|---|---|---|
| committer | Aleksandr Nogikh <nogikh@google.com> | 2023-11-03 13:05:13 +0000 |
| commit | 500bfdc41735bc8d617cbfd4f1ab6b5980c8f1e5 (patch) | |
| tree | 531185e72a0efd70cc4c0a7566712ec9c8807f44 /dashboard/app/handler.go | |
| parent | 7e9533b74d64ae2782ac03bfdc01f7f84840be9e (diff) | |
dashboard: throttle incoming requests
To ensure service stability, let's rate limit incoming requests to our
web endpoints.
Diffstat (limited to 'dashboard/app/handler.go')
| -rw-r--r-- | dashboard/app/handler.go | 39 |
1 files changed, 39 insertions, 0 deletions
diff --git a/dashboard/app/handler.go b/dashboard/app/handler.go index 98827f8ca..7162bd85c 100644 --- a/dashboard/app/handler.go +++ b/dashboard/app/handler.go @@ -33,6 +33,9 @@ func handleContext(fn contextHandler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c := appengine.NewContext(r) c = context.WithValue(c, ¤tURLKey, r.URL.RequestURI()) + if !throttleRequest(c, w, r) { + return + } if err := fn(c, w, r); err != nil { hdr := commonHeaderRaw(c, r) data := &struct { @@ -75,6 +78,42 @@ func handleContext(fn contextHandler) http.Handler { }) } +func throttleRequest(c context.Context, w http.ResponseWriter, r *http.Request) bool { + // AppEngine removes all App Engine-specific headers, which include + // X-Appengine-User-IP and X-Forwarded-For. + // https://cloud.google.com/appengine/docs/standard/reference/request-headers?tab=python#removed_headers + ip := r.Header.Get("X-Appengine-User-IP") + if ip == "" { + ip = r.Header.Get("X-Forwarded-For") + ip, _, _ = strings.Cut(ip, ",") // X-Forwarded-For is a comma-delimited list. + ip = strings.TrimSpace(ip) + } + cron := r.Header.Get("X-Appengine-Cron") != "" + if ip == "" || cron { + log.Infof(c, "cannot throttle request from %q, cron %t", ip, cron) + return true + } + accept, err := ThrottleRequest(c, ip) + if err != nil { + log.Errorf(c, "failed to throttle: %v", err) + } + log.Infof(c, "throttling for %q: %t", ip, accept) + if !accept { + http.Error(w, throttlingErrorMessage(c), http.StatusTooManyRequests) + return false + } + return true +} + +func throttlingErrorMessage(c context.Context) string { + ret := "429 Too Many Requests" + email := getConfig(c).ContactEmail + if email == "" { + return ret + } + return fmt.Sprintf("%s\nPlease contact us at %s if you need access to our data.", ret, email) +} + var currentURLKey = "the URL of the HTTP request in context" func getCurrentURL(c context.Context) string { |
