aboutsummaryrefslogtreecommitdiffstats
path: root/dashboard/app/handler.go
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2023-11-02 19:06:05 +0100
committerAleksandr Nogikh <nogikh@google.com>2023-11-03 13:05:13 +0000
commit500bfdc41735bc8d617cbfd4f1ab6b5980c8f1e5 (patch)
tree531185e72a0efd70cc4c0a7566712ec9c8807f44 /dashboard/app/handler.go
parent7e9533b74d64ae2782ac03bfdc01f7f84840be9e (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.go39
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, &currentURLKey, 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 {